@ztimson/ai-utils 0.6.10 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/asr.js +8 -13
- package/dist/asr.js.map +1 -1
- package/dist/asr.mjs +64 -61
- package/dist/asr.mjs.map +1 -1
- package/dist/audio.d.ts +1 -1
- package/dist/index.js +21 -18
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +229 -205
- package/dist/index.mjs.map +1 -1
- package/dist/llm.d.ts +8 -4
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
import { objectMap as
|
|
3
|
-
import { Anthropic as
|
|
1
|
+
import * as $ from "node:os";
|
|
2
|
+
import { objectMap as k, JSONAttemptParse as g, findByProp as _, JSONSanitize as b, clean as M, Http as E, consoleInterceptor as P, fn as A, ASet as O } from "@ztimson/utils";
|
|
3
|
+
import { Anthropic as v } from "@anthropic-ai/sdk";
|
|
4
4
|
import { OpenAI as U } from "openai";
|
|
5
5
|
import { Worker as x } from "worker_threads";
|
|
6
6
|
import { fileURLToPath as j } from "url";
|
|
@@ -13,48 +13,48 @@ import { $ as N, $Sync as C } from "@ztimson/node-utils";
|
|
|
13
13
|
class q {
|
|
14
14
|
}
|
|
15
15
|
class W extends q {
|
|
16
|
-
constructor(
|
|
17
|
-
super(), this.ai =
|
|
16
|
+
constructor(s, e, t) {
|
|
17
|
+
super(), this.ai = s, this.apiToken = e, this.model = t, this.client = new v({ apiKey: e });
|
|
18
18
|
}
|
|
19
19
|
client;
|
|
20
|
-
toStandard(
|
|
20
|
+
toStandard(s) {
|
|
21
21
|
const e = Date.now(), t = [];
|
|
22
|
-
for (let
|
|
23
|
-
if (typeof
|
|
24
|
-
t.push({ timestamp: e, ...
|
|
22
|
+
for (let m of s)
|
|
23
|
+
if (typeof m.content == "string")
|
|
24
|
+
t.push({ timestamp: e, ...m });
|
|
25
25
|
else {
|
|
26
|
-
const
|
|
26
|
+
const r = m.content?.filter((n) => n.type == "text").map((n) => n.text).join(`
|
|
27
27
|
|
|
28
28
|
`);
|
|
29
|
-
|
|
29
|
+
r && t.push({ timestamp: e, role: m.role, content: r }), m.content.forEach((n) => {
|
|
30
30
|
if (n.type == "tool_use")
|
|
31
31
|
t.push({ timestamp: e, role: "tool", id: n.id, name: n.name, args: n.input, content: void 0 });
|
|
32
32
|
else if (n.type == "tool_result") {
|
|
33
|
-
const
|
|
34
|
-
|
|
33
|
+
const a = t.findLast((i) => i.id == n.tool_use_id);
|
|
34
|
+
a && (a[n.is_error ? "error" : "content"] = n.content);
|
|
35
35
|
}
|
|
36
36
|
});
|
|
37
37
|
}
|
|
38
38
|
return t;
|
|
39
39
|
}
|
|
40
|
-
fromStandard(
|
|
41
|
-
for (let e = 0; e <
|
|
42
|
-
if (
|
|
43
|
-
const t =
|
|
44
|
-
|
|
40
|
+
fromStandard(s) {
|
|
41
|
+
for (let e = 0; e < s.length; e++)
|
|
42
|
+
if (s[e].role == "tool") {
|
|
43
|
+
const t = s[e];
|
|
44
|
+
s.splice(
|
|
45
45
|
e,
|
|
46
46
|
1,
|
|
47
47
|
{ role: "assistant", content: [{ type: "tool_use", id: t.id, name: t.name, input: t.args }] },
|
|
48
48
|
{ role: "user", content: [{ type: "tool_result", tool_use_id: t.id, is_error: !!t.error, content: t.error || t.content }] }
|
|
49
49
|
), e++;
|
|
50
50
|
}
|
|
51
|
-
return
|
|
51
|
+
return s.map(({ timestamp: e, ...t }) => t);
|
|
52
52
|
}
|
|
53
|
-
ask(
|
|
53
|
+
ask(s, e = {}) {
|
|
54
54
|
const t = new AbortController();
|
|
55
|
-
return Object.assign(new Promise(async (
|
|
56
|
-
let
|
|
57
|
-
const n = e.tools || this.ai.options.llm?.tools || [],
|
|
55
|
+
return Object.assign(new Promise(async (m) => {
|
|
56
|
+
let r = this.fromStandard([...e.history || [], { role: "user", content: s, timestamp: Date.now() }]);
|
|
57
|
+
const n = e.tools || this.ai.options.llm?.tools || [], a = {
|
|
58
58
|
model: e.model || this.model,
|
|
59
59
|
max_tokens: e.max_tokens || this.ai.options.llm?.max_tokens || 4096,
|
|
60
60
|
system: e.system || this.ai.options.llm?.system || "",
|
|
@@ -64,46 +64,46 @@ class W extends q {
|
|
|
64
64
|
description: d.description,
|
|
65
65
|
input_schema: {
|
|
66
66
|
type: "object",
|
|
67
|
-
properties: d.args ?
|
|
67
|
+
properties: d.args ? k(d.args, (c, l) => ({ ...l, required: void 0 })) : {},
|
|
68
68
|
required: d.args ? Object.entries(d.args).filter((c) => c[1].required).map((c) => c[0]) : []
|
|
69
69
|
},
|
|
70
70
|
fn: void 0
|
|
71
71
|
})),
|
|
72
|
-
messages:
|
|
72
|
+
messages: r,
|
|
73
73
|
stream: !!e.stream
|
|
74
74
|
};
|
|
75
|
-
let
|
|
75
|
+
let i, o = !0;
|
|
76
76
|
do {
|
|
77
|
-
if (
|
|
77
|
+
if (i = await this.client.messages.create(a).catch((c) => {
|
|
78
78
|
throw c.message += `
|
|
79
79
|
|
|
80
80
|
Messages:
|
|
81
|
-
${JSON.stringify(
|
|
81
|
+
${JSON.stringify(r, null, 2)}`, c;
|
|
82
82
|
}), e.stream) {
|
|
83
|
-
|
|
83
|
+
o ? o = !1 : e.stream({ text: `
|
|
84
84
|
|
|
85
|
-
` }),
|
|
86
|
-
for await (const c of
|
|
85
|
+
` }), i.content = [];
|
|
86
|
+
for await (const c of i) {
|
|
87
87
|
if (t.signal.aborted) break;
|
|
88
88
|
if (c.type === "content_block_start")
|
|
89
|
-
c.content_block.type === "text" ?
|
|
89
|
+
c.content_block.type === "text" ? i.content.push({ type: "text", text: "" }) : c.content_block.type === "tool_use" && i.content.push({ type: "tool_use", id: c.content_block.id, name: c.content_block.name, input: "" });
|
|
90
90
|
else if (c.type === "content_block_delta")
|
|
91
91
|
if (c.delta.type === "text_delta") {
|
|
92
92
|
const l = c.delta.text;
|
|
93
|
-
|
|
94
|
-
} else c.delta.type === "input_json_delta" && (
|
|
93
|
+
i.content.at(-1).text += l, e.stream({ text: l });
|
|
94
|
+
} else c.delta.type === "input_json_delta" && (i.content.at(-1).input += c.delta.partial_json);
|
|
95
95
|
else if (c.type === "content_block_stop") {
|
|
96
|
-
const l =
|
|
96
|
+
const l = i.content.at(-1);
|
|
97
97
|
l.input != null && (l.input = l.input ? g(l.input, {}) : {});
|
|
98
98
|
} else if (c.type === "message_stop")
|
|
99
99
|
break;
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
|
-
const d =
|
|
102
|
+
const d = i.content.filter((c) => c.type === "tool_use");
|
|
103
103
|
if (d.length && !t.signal.aborted) {
|
|
104
|
-
|
|
104
|
+
r.push({ role: "assistant", content: i.content });
|
|
105
105
|
const c = await Promise.all(d.map(async (l) => {
|
|
106
|
-
const p = n.find(
|
|
106
|
+
const p = n.find(_("name", l.name));
|
|
107
107
|
if (e.stream && e.stream({ tool: l.name }), !p) return { tool_use_id: l.id, is_error: !0, content: "Tool not found" };
|
|
108
108
|
try {
|
|
109
109
|
const u = await p.fn(l.input, e?.stream, this.ai);
|
|
@@ -112,45 +112,45 @@ ${JSON.stringify(s, null, 2)}`, c;
|
|
|
112
112
|
return { type: "tool_result", tool_use_id: l.id, is_error: !0, content: u?.message || u?.toString() || "Unknown" };
|
|
113
113
|
}
|
|
114
114
|
}));
|
|
115
|
-
|
|
115
|
+
r.push({ role: "user", content: c }), a.messages = r;
|
|
116
116
|
}
|
|
117
|
-
} while (!t.signal.aborted &&
|
|
118
|
-
|
|
117
|
+
} while (!t.signal.aborted && i.content.some((d) => d.type === "tool_use"));
|
|
118
|
+
r.push({ role: "assistant", content: i.content.filter((d) => d.type == "text").map((d) => d.text).join(`
|
|
119
119
|
|
|
120
|
-
`) }),
|
|
120
|
+
`) }), r = this.toStandard(r), e.stream && e.stream({ done: !0 }), e.history && e.history.splice(0, e.history.length, ...r), m(r.at(-1)?.content);
|
|
121
121
|
}), { abort: () => t.abort() });
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
124
|
class w extends q {
|
|
125
|
-
constructor(
|
|
126
|
-
super(), this.ai =
|
|
125
|
+
constructor(s, e, t, m) {
|
|
126
|
+
super(), this.ai = s, this.host = e, this.token = t, this.model = m, this.client = new U(M({
|
|
127
127
|
baseURL: e,
|
|
128
128
|
apiKey: t
|
|
129
129
|
}));
|
|
130
130
|
}
|
|
131
131
|
client;
|
|
132
|
-
toStandard(
|
|
133
|
-
for (let e = 0; e <
|
|
134
|
-
const t =
|
|
132
|
+
toStandard(s) {
|
|
133
|
+
for (let e = 0; e < s.length; e++) {
|
|
134
|
+
const t = s[e];
|
|
135
135
|
if (t.role === "assistant" && t.tool_calls) {
|
|
136
|
-
const
|
|
136
|
+
const m = t.tool_calls.map((r) => ({
|
|
137
137
|
role: "tool",
|
|
138
|
-
id:
|
|
139
|
-
name:
|
|
140
|
-
args: g(
|
|
138
|
+
id: r.id,
|
|
139
|
+
name: r.function.name,
|
|
140
|
+
args: g(r.function.arguments, {}),
|
|
141
141
|
timestamp: t.timestamp
|
|
142
142
|
}));
|
|
143
|
-
|
|
143
|
+
s.splice(e, 1, ...m), e += m.length - 1;
|
|
144
144
|
} else if (t.role === "tool" && t.content) {
|
|
145
|
-
const
|
|
146
|
-
|
|
145
|
+
const m = s.find((r) => t.tool_call_id == r.id);
|
|
146
|
+
m && (t.content.includes('"error":') ? m.error = t.content : m.content = t.content), s.splice(e, 1), e--;
|
|
147
147
|
}
|
|
148
|
-
|
|
148
|
+
s[e]?.timestamp || (s[e].timestamp = Date.now());
|
|
149
149
|
}
|
|
150
|
-
return
|
|
150
|
+
return s;
|
|
151
151
|
}
|
|
152
|
-
fromStandard(
|
|
153
|
-
return
|
|
152
|
+
fromStandard(s) {
|
|
153
|
+
return s.reduce((e, t) => {
|
|
154
154
|
if (t.role === "tool")
|
|
155
155
|
e.push({
|
|
156
156
|
role: "assistant",
|
|
@@ -164,39 +164,39 @@ class w extends q {
|
|
|
164
164
|
content: t.error || t.content
|
|
165
165
|
});
|
|
166
166
|
else {
|
|
167
|
-
const { timestamp:
|
|
168
|
-
e.push(
|
|
167
|
+
const { timestamp: m, ...r } = t;
|
|
168
|
+
e.push(r);
|
|
169
169
|
}
|
|
170
170
|
return e;
|
|
171
171
|
}, []);
|
|
172
172
|
}
|
|
173
|
-
ask(
|
|
173
|
+
ask(s, e = {}) {
|
|
174
174
|
const t = new AbortController();
|
|
175
|
-
return Object.assign(new Promise(async (
|
|
175
|
+
return Object.assign(new Promise(async (m, r) => {
|
|
176
176
|
e.system && e.history?.[0]?.role != "system" && e.history?.splice(0, 0, { role: "system", content: e.system, timestamp: Date.now() });
|
|
177
|
-
let n = this.fromStandard([...e.history || [], { role: "user", content:
|
|
178
|
-
const
|
|
177
|
+
let n = this.fromStandard([...e.history || [], { role: "user", content: s, timestamp: Date.now() }]);
|
|
178
|
+
const a = e.tools || this.ai.options.llm?.tools || [], i = {
|
|
179
179
|
model: e.model || this.model,
|
|
180
180
|
messages: n,
|
|
181
181
|
stream: !!e.stream,
|
|
182
182
|
max_tokens: e.max_tokens || this.ai.options.llm?.max_tokens || 4096,
|
|
183
183
|
temperature: e.temperature || this.ai.options.llm?.temperature || 0.7,
|
|
184
|
-
tools:
|
|
184
|
+
tools: a.map((c) => ({
|
|
185
185
|
type: "function",
|
|
186
186
|
function: {
|
|
187
187
|
name: c.name,
|
|
188
188
|
description: c.description,
|
|
189
189
|
parameters: {
|
|
190
190
|
type: "object",
|
|
191
|
-
properties: c.args ?
|
|
191
|
+
properties: c.args ? k(c.args, (l, p) => ({ ...p, required: void 0 })) : {},
|
|
192
192
|
required: c.args ? Object.entries(c.args).filter((l) => l[1].required).map((l) => l[0]) : []
|
|
193
193
|
}
|
|
194
194
|
}
|
|
195
195
|
}))
|
|
196
196
|
};
|
|
197
|
-
let
|
|
197
|
+
let o, d = !0;
|
|
198
198
|
do {
|
|
199
|
-
if (
|
|
199
|
+
if (o = await this.client.chat.completions.create(i).catch((l) => {
|
|
200
200
|
throw l.message += `
|
|
201
201
|
|
|
202
202
|
Messages:
|
|
@@ -204,17 +204,17 @@ ${JSON.stringify(n, null, 2)}`, l;
|
|
|
204
204
|
}), e.stream) {
|
|
205
205
|
d ? d = !1 : e.stream({ text: `
|
|
206
206
|
|
|
207
|
-
` }),
|
|
208
|
-
for await (const l of
|
|
207
|
+
` }), o.choices = [{ message: { content: "", tool_calls: [] } }];
|
|
208
|
+
for await (const l of o) {
|
|
209
209
|
if (t.signal.aborted) break;
|
|
210
|
-
l.choices[0].delta.content && (
|
|
210
|
+
l.choices[0].delta.content && (o.choices[0].message.content += l.choices[0].delta.content, e.stream({ text: l.choices[0].delta.content })), l.choices[0].delta.tool_calls && (o.choices[0].message.tool_calls = l.choices[0].delta.tool_calls);
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
|
-
const c =
|
|
213
|
+
const c = o.choices[0].message.tool_calls || [];
|
|
214
214
|
if (c.length && !t.signal.aborted) {
|
|
215
|
-
n.push(
|
|
215
|
+
n.push(o.choices[0].message);
|
|
216
216
|
const l = await Promise.all(c.map(async (p) => {
|
|
217
|
-
const u =
|
|
217
|
+
const u = a?.find(_("name", p.function.name));
|
|
218
218
|
if (e.stream && e.stream({ tool: p.function.name }), !u) return { role: "tool", tool_call_id: p.id, content: '{"error": "Tool not found"}' };
|
|
219
219
|
try {
|
|
220
220
|
const f = g(p.function.arguments, {}), y = await u.fn(f, e.stream, this.ai);
|
|
@@ -223,46 +223,46 @@ ${JSON.stringify(n, null, 2)}`, l;
|
|
|
223
223
|
return { role: "tool", tool_call_id: p.id, content: b({ error: f?.message || f?.toString() || "Unknown" }) };
|
|
224
224
|
}
|
|
225
225
|
}));
|
|
226
|
-
n.push(...l),
|
|
226
|
+
n.push(...l), i.messages = n;
|
|
227
227
|
}
|
|
228
|
-
} while (!t.signal.aborted &&
|
|
229
|
-
n.push({ role: "assistant", content:
|
|
228
|
+
} while (!t.signal.aborted && o.choices?.[0]?.message?.tool_calls?.length);
|
|
229
|
+
n.push({ role: "assistant", content: o.choices[0].message.content || "" }), 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);
|
|
230
230
|
}), { abort: () => t.abort() });
|
|
231
231
|
}
|
|
232
232
|
}
|
|
233
233
|
class z {
|
|
234
|
-
constructor(
|
|
235
|
-
this.ai =
|
|
234
|
+
constructor(s) {
|
|
235
|
+
this.ai = s, s.options.llm?.models && Object.entries(s.options.llm.models).forEach(([e, t]) => {
|
|
236
236
|
this.defaultModel || (this.defaultModel = e), t.proto == "anthropic" ? this.models[e] = new W(this.ai, t.token, e) : t.proto == "ollama" ? this.models[e] = new w(this.ai, t.host, "not-needed", e) : t.proto == "openai" && (this.models[e] = new w(this.ai, t.host || null, t.token, e));
|
|
237
237
|
});
|
|
238
238
|
}
|
|
239
|
-
models = {};
|
|
240
239
|
defaultModel;
|
|
240
|
+
models = {};
|
|
241
241
|
/**
|
|
242
242
|
* Chat with LLM
|
|
243
243
|
* @param {string} message Question
|
|
244
244
|
* @param {LLMRequest} options Configuration options and chat history
|
|
245
245
|
* @returns {{abort: () => void, response: Promise<string>}} Function to abort response and chat history
|
|
246
246
|
*/
|
|
247
|
-
ask(
|
|
247
|
+
ask(s, e = {}) {
|
|
248
248
|
const t = e.model || this.defaultModel;
|
|
249
249
|
if (!this.models[t]) throw new Error(`Model does not exist: ${t}`);
|
|
250
|
-
let
|
|
250
|
+
let m = () => {
|
|
251
251
|
};
|
|
252
|
-
return Object.assign(new Promise(async (
|
|
252
|
+
return Object.assign(new Promise(async (r) => {
|
|
253
253
|
if (e.history || (e.history = []), e.memory) {
|
|
254
254
|
e.system = (e.system || "") + `
|
|
255
255
|
You have passive persistent memory never make any mention of your memory capabilities and what you can/cannot remember
|
|
256
256
|
`;
|
|
257
|
-
const
|
|
257
|
+
const a = async (o, d, c = 50) => {
|
|
258
258
|
const [l, p] = await Promise.all([
|
|
259
259
|
d ? this.embedding(d) : Promise.resolve(null),
|
|
260
|
-
|
|
260
|
+
o ? this.embedding(o) : Promise.resolve(null)
|
|
261
261
|
]);
|
|
262
262
|
return (e.memory || []).map((u) => ({ ...u, score: l ? this.cosineSimilarity(u.embeddings[0], l[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, c);
|
|
263
|
-
},
|
|
264
|
-
|
|
265
|
-
` +
|
|
263
|
+
}, i = await a(s);
|
|
264
|
+
i.length && e.history.push({ role: "assistant", content: `Things I remembered:
|
|
265
|
+
` + i.map((o) => `${o.owner}: ${o.fact}`).join(`
|
|
266
266
|
`) }), e.tools = [...e.tools || [], {
|
|
267
267
|
name: "read_memory",
|
|
268
268
|
description: "Check your long-term memory for more information",
|
|
@@ -271,32 +271,32 @@ You have passive persistent memory never make any mention of your memory capabil
|
|
|
271
271
|
query: { type: "string", description: "Search memory based on a query, can be used with or without subject argument" },
|
|
272
272
|
limit: { type: "number", description: "Result limit, default 5" }
|
|
273
273
|
},
|
|
274
|
-
fn: (
|
|
275
|
-
if (!
|
|
276
|
-
return
|
|
274
|
+
fn: (o) => {
|
|
275
|
+
if (!o.subject && !o.query) throw new Error("Either a subject or query argument is required");
|
|
276
|
+
return a(o.query, o.subject, o.limit || 5);
|
|
277
277
|
}
|
|
278
278
|
}];
|
|
279
279
|
}
|
|
280
|
-
const n = await this.models[t].ask(
|
|
280
|
+
const n = await this.models[t].ask(s, e);
|
|
281
281
|
if (e.memory) {
|
|
282
|
-
const
|
|
283
|
-
|
|
282
|
+
const a = e.history?.findIndex((i) => i.role == "assistant" && i.content.startsWith("Things I remembered:"));
|
|
283
|
+
a != null && a >= 0 && e.history?.splice(a, 1);
|
|
284
284
|
}
|
|
285
285
|
if (e.compress || e.memory) {
|
|
286
|
-
let
|
|
286
|
+
let a = null;
|
|
287
287
|
if (e.compress)
|
|
288
|
-
|
|
288
|
+
a = await this.ai.language.compressHistory(e.history, e.compress.max, e.compress.min, e), e.history.splice(0, e.history.length, ...a.history);
|
|
289
289
|
else {
|
|
290
|
-
const
|
|
291
|
-
|
|
290
|
+
const i = e.history?.findLastIndex((o) => o.role == "user") ?? -1;
|
|
291
|
+
a = await this.ai.language.compressHistory(i != -1 ? e.history.slice(i) : e.history, 0, 0, e);
|
|
292
292
|
}
|
|
293
293
|
if (e.memory) {
|
|
294
|
-
const
|
|
295
|
-
e.memory.splice(0, e.memory.length, ...
|
|
294
|
+
const i = e.memory.filter((o) => !a.memory.some((d) => this.cosineSimilarity(o.embeddings[1], d.embeddings[1]) > 0.8)).concat(a.memory);
|
|
295
|
+
e.memory.splice(0, e.memory.length, ...i);
|
|
296
296
|
}
|
|
297
297
|
}
|
|
298
|
-
return
|
|
299
|
-
}), { abort:
|
|
298
|
+
return r(n);
|
|
299
|
+
}), { abort: m });
|
|
300
300
|
}
|
|
301
301
|
/**
|
|
302
302
|
* Compress chat history to reduce context size
|
|
@@ -306,22 +306,24 @@ You have passive persistent memory never make any mention of your memory capabil
|
|
|
306
306
|
* @param {LLMRequest} options LLM options
|
|
307
307
|
* @returns {Promise<LLMMessage[]>} New chat history will summary at index 0
|
|
308
308
|
*/
|
|
309
|
-
async compressHistory(
|
|
310
|
-
if (this.estimateTokens(
|
|
311
|
-
let
|
|
312
|
-
for (let u of
|
|
313
|
-
if (n += this.estimateTokens(u.content), n < t)
|
|
309
|
+
async compressHistory(s, e, t, m) {
|
|
310
|
+
if (this.estimateTokens(s) < e) return { history: s, memory: [] };
|
|
311
|
+
let r = 0, n = 0;
|
|
312
|
+
for (let u of s.toReversed())
|
|
313
|
+
if (n += this.estimateTokens(u.content), n < t) r++;
|
|
314
314
|
else break;
|
|
315
|
-
if (
|
|
316
|
-
const
|
|
315
|
+
if (s.length <= r) return { history: s, memory: [] };
|
|
316
|
+
const a = s[0].role == "system" ? s[0] : null, i = r == 0 ? [] : s.slice(-r), o = (r == 0 ? s : s.slice(0, -r)).filter((u) => u.role === "assistant" || u.role === "user"), d = await this.json(o.map((u) => `${u.role}: ${u.content}`).join(`
|
|
317
317
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
318
|
+
`), "{summary: string, facts: [[subject, fact]]}", {
|
|
319
|
+
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.",
|
|
320
|
+
model: m?.model,
|
|
321
|
+
temperature: m?.temperature || 0.3
|
|
322
|
+
}), c = /* @__PURE__ */ new Date(), l = await Promise.all((d?.facts || [])?.map(async ([u, f]) => {
|
|
321
323
|
const y = await Promise.all([this.embedding(u), this.embedding(`${u}: ${f}`)]);
|
|
322
324
|
return { owner: u, fact: f, embeddings: [y[0][0].embedding, y[1][0].embedding], timestamp: c };
|
|
323
|
-
})), p = [{ role: "assistant", content: `Conversation Summary: ${d?.summary}`, timestamp: Date.now() }, ...
|
|
324
|
-
return
|
|
325
|
+
})), p = [{ role: "assistant", content: `Conversation Summary: ${d?.summary}`, timestamp: Date.now() }, ...i];
|
|
326
|
+
return a && p.splice(0, 0, a), { history: p, memory: l };
|
|
325
327
|
}
|
|
326
328
|
/**
|
|
327
329
|
* Compare the difference between embeddings (calculates the angle between two vectors)
|
|
@@ -329,12 +331,12 @@ ${a.map((u) => `${u.role}: ${u.content}`).join(`
|
|
|
329
331
|
* @param {number[]} v2 Second embedding / vector for comparison
|
|
330
332
|
* @returns {number} Similarity values 0-1: 0 = unique, 1 = identical
|
|
331
333
|
*/
|
|
332
|
-
cosineSimilarity(
|
|
333
|
-
if (
|
|
334
|
-
let t = 0,
|
|
335
|
-
for (let
|
|
336
|
-
t +=
|
|
337
|
-
const n = Math.sqrt(
|
|
334
|
+
cosineSimilarity(s, e) {
|
|
335
|
+
if (s.length !== e.length) throw new Error("Vectors must be same length");
|
|
336
|
+
let t = 0, m = 0, r = 0;
|
|
337
|
+
for (let a = 0; a < s.length; a++)
|
|
338
|
+
t += s[a] * e[a], m += s[a] * s[a], r += e[a] * e[a];
|
|
339
|
+
const n = Math.sqrt(m) * Math.sqrt(r);
|
|
338
340
|
return n === 0 ? 0 : t / n;
|
|
339
341
|
}
|
|
340
342
|
/**
|
|
@@ -344,26 +346,26 @@ ${a.map((u) => `${u.role}: ${u.content}`).join(`
|
|
|
344
346
|
* @param {number} overlapTokens Includes previous X tokens to provide continuity to AI (In addition to max tokens)
|
|
345
347
|
* @returns {string[]} Chunked strings
|
|
346
348
|
*/
|
|
347
|
-
chunk(
|
|
348
|
-
const
|
|
349
|
-
const l =
|
|
350
|
-
return typeof c == "object" && !Array.isArray(c) ?
|
|
351
|
-
}) : [], n = (typeof
|
|
352
|
-
`)).flatMap((
|
|
353
|
-
`]),
|
|
354
|
-
for (let
|
|
355
|
-
let
|
|
349
|
+
chunk(s, e = 500, t = 50) {
|
|
350
|
+
const m = (i, o = "") => i ? Object.entries(i).flatMap(([d, c]) => {
|
|
351
|
+
const l = o ? `${o}${isNaN(+d) ? `.${d}` : `[${d}]`}` : d;
|
|
352
|
+
return typeof c == "object" && !Array.isArray(c) ? m(c, l) : `${l}: ${Array.isArray(c) ? c.join(", ") : c}`;
|
|
353
|
+
}) : [], n = (typeof s == "object" ? m(s) : s.split(`
|
|
354
|
+
`)).flatMap((i) => [...i.split(/\s+/).filter(Boolean), `
|
|
355
|
+
`]), a = [];
|
|
356
|
+
for (let i = 0; i < n.length; ) {
|
|
357
|
+
let o = "", d = i;
|
|
356
358
|
for (; d < n.length; ) {
|
|
357
|
-
const l =
|
|
359
|
+
const l = o + (o ? " " : "") + n[d];
|
|
358
360
|
if (this.estimateTokens(l.replace(/\s*\n\s*/g, `
|
|
359
|
-
`)) > e &&
|
|
360
|
-
|
|
361
|
+
`)) > e && o) break;
|
|
362
|
+
o = l, d++;
|
|
361
363
|
}
|
|
362
|
-
const c =
|
|
364
|
+
const c = o.replace(/\s*\n\s*/g, `
|
|
363
365
|
`).trim();
|
|
364
|
-
c &&
|
|
366
|
+
c && a.push(c), i = Math.max(d - t, d === i ? i + 1 : d);
|
|
365
367
|
}
|
|
366
|
-
return
|
|
368
|
+
return a;
|
|
367
369
|
}
|
|
368
370
|
/**
|
|
369
371
|
* Create a vector representation of a string
|
|
@@ -372,20 +374,20 @@ ${a.map((u) => `${u.role}: ${u.content}`).join(`
|
|
|
372
374
|
* @param {number} overlapTokens Includes previous X tokens to provide continuity to AI (In addition to max tokens)
|
|
373
375
|
* @returns {Promise<Awaited<{index: number, embedding: number[], text: string, tokens: number}>[]>} Chunked embeddings
|
|
374
376
|
*/
|
|
375
|
-
embedding(
|
|
376
|
-
const
|
|
377
|
-
const
|
|
378
|
-
|
|
377
|
+
embedding(s, e = 500, t = 50) {
|
|
378
|
+
const m = (n) => new Promise((a, i) => {
|
|
379
|
+
const o = new x(S(T(j(import.meta.url)), "embedder.js")), d = ({ embedding: l }) => {
|
|
380
|
+
o.terminate(), a(l);
|
|
379
381
|
}, c = (l) => {
|
|
380
|
-
|
|
382
|
+
o.terminate(), i(l);
|
|
381
383
|
};
|
|
382
|
-
|
|
383
|
-
l !== 0 &&
|
|
384
|
-
}),
|
|
385
|
-
}),
|
|
386
|
-
return Promise.all(
|
|
387
|
-
index:
|
|
388
|
-
embedding: await
|
|
384
|
+
o.on("message", d), o.on("error", c), o.on("exit", (l) => {
|
|
385
|
+
l !== 0 && i(new Error(`Worker exited with code ${l}`));
|
|
386
|
+
}), o.postMessage({ text: n, model: this.ai.options?.embedder || "bge-small-en-v1.5", modelDir: this.ai.options.path });
|
|
387
|
+
}), r = this.chunk(s, e, t);
|
|
388
|
+
return Promise.all(r.map(async (n, a) => ({
|
|
389
|
+
index: a,
|
|
390
|
+
embedding: await m(n),
|
|
389
391
|
text: n,
|
|
390
392
|
tokens: this.estimateTokens(n)
|
|
391
393
|
})));
|
|
@@ -395,8 +397,8 @@ ${a.map((u) => `${u.role}: ${u.content}`).join(`
|
|
|
395
397
|
* @param history Object to size
|
|
396
398
|
* @returns {number} Rough token count
|
|
397
399
|
*/
|
|
398
|
-
estimateTokens(
|
|
399
|
-
const e = JSON.stringify(
|
|
400
|
+
estimateTokens(s) {
|
|
401
|
+
const e = JSON.stringify(s);
|
|
400
402
|
return Math.ceil(e.length / 4 * 1.2);
|
|
401
403
|
}
|
|
402
404
|
/**
|
|
@@ -405,22 +407,27 @@ ${a.map((u) => `${u.role}: ${u.content}`).join(`
|
|
|
405
407
|
* @param {string} searchTerms Multiple search terms to check against target
|
|
406
408
|
* @returns {{avg: number, max: number, similarities: number[]}} Similarity values 0-1: 0 = unique, 1 = identical
|
|
407
409
|
*/
|
|
408
|
-
fuzzyMatch(
|
|
410
|
+
fuzzyMatch(s, ...e) {
|
|
409
411
|
if (e.length < 2) throw new Error("Requires at least 2 strings to compare");
|
|
410
|
-
const t = (n,
|
|
411
|
-
return { avg:
|
|
412
|
+
const t = (n, a = 10) => n.toLowerCase().split("").map((i, o) => i.charCodeAt(0) * (o + 1) % a / a).slice(0, a), m = t(s), r = e.map((n) => t(n)).map((n) => this.cosineSimilarity(m, n));
|
|
413
|
+
return { avg: r.reduce((n, a) => n + a, 0) / r.length, max: Math.max(...r), similarities: r };
|
|
412
414
|
}
|
|
413
415
|
/**
|
|
414
416
|
* Ask a question with JSON response
|
|
415
|
-
* @param {string}
|
|
417
|
+
* @param {string} text Text to process
|
|
418
|
+
* @param {string} schema JSON schema the AI should match
|
|
416
419
|
* @param {LLMRequest} options Configuration options and chat history
|
|
417
420
|
* @returns {Promise<{} | {} | RegExpExecArray | null>}
|
|
418
421
|
*/
|
|
419
|
-
async json(
|
|
420
|
-
let
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
422
|
+
async json(s, e, t) {
|
|
423
|
+
let m = await this.ask(s, { ...t, system: (t?.system ? `${t.system}
|
|
424
|
+
` : "") + `Only respond using a JSON code block matching this schema:
|
|
425
|
+
\`\`\`json
|
|
426
|
+
${e}
|
|
427
|
+
\`\`\`` });
|
|
428
|
+
if (!m) return {};
|
|
429
|
+
const r = /```(?:.+)?\s*([\s\S]*?)```/.exec(m), n = r ? r[1].trim() : m;
|
|
430
|
+
return g(n, {});
|
|
424
431
|
}
|
|
425
432
|
/**
|
|
426
433
|
* Create a summary of some text
|
|
@@ -429,55 +436,72 @@ ${a.map((u) => `${u.role}: ${u.content}`).join(`
|
|
|
429
436
|
* @param options LLM request options
|
|
430
437
|
* @returns {Promise<string>} Summary
|
|
431
438
|
*/
|
|
432
|
-
summarize(
|
|
433
|
-
return this.ask(
|
|
439
|
+
summarize(s, e, t) {
|
|
440
|
+
return this.ask(s, { system: `Generate a brief summary <= ${e} tokens. Output nothing else`, temperature: 0.3, ...t });
|
|
434
441
|
}
|
|
435
442
|
}
|
|
436
443
|
class I {
|
|
437
|
-
constructor(
|
|
438
|
-
this.ai =
|
|
444
|
+
constructor(s) {
|
|
445
|
+
this.ai = s;
|
|
439
446
|
}
|
|
440
|
-
asr(
|
|
441
|
-
const { model: t = this.ai.options.asr || "whisper-base", speaker:
|
|
442
|
-
let
|
|
447
|
+
asr(s, e = {}) {
|
|
448
|
+
const { model: t = this.ai.options.asr || "whisper-base", speaker: m = !1 } = e;
|
|
449
|
+
let r = !1;
|
|
443
450
|
const n = () => {
|
|
444
|
-
|
|
445
|
-
}
|
|
451
|
+
r = !0;
|
|
452
|
+
};
|
|
453
|
+
let a = new Promise((i, o) => {
|
|
446
454
|
const d = new x(S(T(j(import.meta.url)), "asr.js")), c = ({ text: p, warning: u, error: f }) => {
|
|
447
|
-
d.terminate(), !
|
|
455
|
+
d.terminate(), !r && (f ? o(new Error(f)) : (u && console.warn(u), i(p)));
|
|
448
456
|
}, l = (p) => {
|
|
449
|
-
d.terminate(),
|
|
457
|
+
d.terminate(), r || o(p);
|
|
450
458
|
};
|
|
451
459
|
d.on("message", c), d.on("error", l), d.on("exit", (p) => {
|
|
452
|
-
p !== 0 && !
|
|
453
|
-
}), d.postMessage({ file:
|
|
460
|
+
p !== 0 && !r && o(new Error(`Worker exited with code ${p}`));
|
|
461
|
+
}), d.postMessage({ file: s, model: t, speaker: m, modelDir: this.ai.options.path, token: this.ai.options.hfToken });
|
|
454
462
|
});
|
|
455
|
-
|
|
463
|
+
if (e.speaker == "id") {
|
|
464
|
+
if (!this.ai.language.defaultModel) throw new Error("Configure an LLM for advanced ASR speaker detection");
|
|
465
|
+
a = a.then(async (i) => {
|
|
466
|
+
if (!i) return i;
|
|
467
|
+
let o = this.ai.language.chunk(i, 500, 0);
|
|
468
|
+
o.length > 4 && (o = [...o.slice(0, 3), o.at(-1)]);
|
|
469
|
+
const d = await this.ai.language.json(o.join(`
|
|
470
|
+
`), '{1: "Detected Name"}', {
|
|
471
|
+
system: "Use this following transcript to identify speakers. Only identify speakers you are sure about",
|
|
472
|
+
temperature: 0.1
|
|
473
|
+
});
|
|
474
|
+
return Object.entries(d).forEach(([c, l]) => {
|
|
475
|
+
i = i.replaceAll(`[Speaker ${c}]`, `[${l}]`);
|
|
476
|
+
}), i;
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
return Object.assign(a, { abort: n });
|
|
456
480
|
}
|
|
457
481
|
canDiarization = R;
|
|
458
482
|
}
|
|
459
483
|
class J {
|
|
460
|
-
constructor(
|
|
461
|
-
this.ai =
|
|
484
|
+
constructor(s) {
|
|
485
|
+
this.ai = s;
|
|
462
486
|
}
|
|
463
487
|
/**
|
|
464
488
|
* Convert image to text using Optical Character Recognition
|
|
465
489
|
* @param {string} path Path to image
|
|
466
490
|
* @returns {AbortablePromise<string | null>} Promise of extracted text with abort method
|
|
467
491
|
*/
|
|
468
|
-
ocr(
|
|
492
|
+
ocr(s) {
|
|
469
493
|
let e;
|
|
470
|
-
const t = new Promise(async (
|
|
494
|
+
const t = new Promise(async (m) => {
|
|
471
495
|
e = await L(this.ai.options.ocr || "eng", 2, { cachePath: this.ai.options.path });
|
|
472
|
-
const { data:
|
|
473
|
-
await e.terminate(),
|
|
496
|
+
const { data: r } = await e.recognize(s);
|
|
497
|
+
await e.terminate(), m(r.text.trim() || null);
|
|
474
498
|
});
|
|
475
499
|
return Object.assign(t, { abort: () => e?.terminate() });
|
|
476
500
|
}
|
|
477
501
|
}
|
|
478
|
-
class
|
|
479
|
-
constructor(
|
|
480
|
-
this.options =
|
|
502
|
+
class re {
|
|
503
|
+
constructor(s) {
|
|
504
|
+
this.options = s, s.path || (s.path = $.tmpdir()), process.env.TRANSFORMERS_CACHE = s.path, this.audio = new I(this), this.language = new z(this), this.vision = new J(this);
|
|
481
505
|
}
|
|
482
506
|
/** Audio processing AI */
|
|
483
507
|
audio;
|
|
@@ -503,15 +527,15 @@ const H = {
|
|
|
503
527
|
language: { type: "string", description: "Execution language", enum: ["cli", "node", "python"], required: !0 },
|
|
504
528
|
code: { type: "string", description: "Code to execute", required: !0 }
|
|
505
529
|
},
|
|
506
|
-
fn: async (h,
|
|
530
|
+
fn: async (h, s, e) => {
|
|
507
531
|
try {
|
|
508
532
|
switch (h.type) {
|
|
509
533
|
case "bash":
|
|
510
|
-
return await H.fn({ command: h.code },
|
|
534
|
+
return await H.fn({ command: h.code }, s, e);
|
|
511
535
|
case "node":
|
|
512
|
-
return await F.fn({ code: h.code },
|
|
536
|
+
return await F.fn({ code: h.code }, s, e);
|
|
513
537
|
case "python":
|
|
514
|
-
return await G.fn({ code: h.code },
|
|
538
|
+
return await G.fn({ code: h.code }, s, e);
|
|
515
539
|
}
|
|
516
540
|
} catch (t) {
|
|
517
541
|
return { error: t?.message || t.toString() };
|
|
@@ -526,7 +550,7 @@ const H = {
|
|
|
526
550
|
headers: { type: "object", description: "HTTP headers to send", default: {} },
|
|
527
551
|
body: { type: "object", description: "HTTP body to send" }
|
|
528
552
|
},
|
|
529
|
-
fn: (h) => new
|
|
553
|
+
fn: (h) => new E({ url: h.url, headers: h.headers }).request({ method: h.method || "GET", body: h.body })
|
|
530
554
|
}, F = {
|
|
531
555
|
name: "exec_javascript",
|
|
532
556
|
description: "Execute commonjs javascript",
|
|
@@ -534,8 +558,8 @@ const H = {
|
|
|
534
558
|
code: { type: "string", description: "CommonJS javascript", required: !0 }
|
|
535
559
|
},
|
|
536
560
|
fn: async (h) => {
|
|
537
|
-
const
|
|
538
|
-
return { ...
|
|
561
|
+
const s = P(null), e = await A({ console: s }, h.code, !0).catch((t) => s.output.error.push(t));
|
|
562
|
+
return { ...s.output, return: e, stdout: void 0, stderr: void 0 };
|
|
539
563
|
}
|
|
540
564
|
}, G = {
|
|
541
565
|
name: "exec_javascript",
|
|
@@ -552,24 +576,24 @@ const H = {
|
|
|
552
576
|
focus: { type: "string", description: 'Optional: What aspect to focus on (e.g., "pricing", "features", "contact info")' }
|
|
553
577
|
},
|
|
554
578
|
fn: async (h) => {
|
|
555
|
-
const
|
|
579
|
+
const s = await fetch(h.url, { headers: { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" } }).then((n) => n.text()).catch((n) => {
|
|
556
580
|
throw new Error(`Failed to fetch: ${n.message}`);
|
|
557
|
-
}), e = D.load(
|
|
581
|
+
}), e = D.load(s);
|
|
558
582
|
e('script, style, nav, footer, header, aside, iframe, noscript, [role="navigation"], [role="banner"], .ad, .ads, .cookie, .popup').remove();
|
|
559
583
|
const t = {
|
|
560
584
|
title: e('meta[property="og:title"]').attr("content") || e("title").text() || "",
|
|
561
585
|
description: e('meta[name="description"]').attr("content") || e('meta[property="og:description"]').attr("content") || ""
|
|
562
586
|
};
|
|
563
|
-
let
|
|
564
|
-
const
|
|
565
|
-
for (const n of
|
|
566
|
-
const
|
|
567
|
-
if (
|
|
568
|
-
|
|
587
|
+
let m = "";
|
|
588
|
+
const r = ["article", "main", '[role="main"]', ".content", ".post", ".entry", "body"];
|
|
589
|
+
for (const n of r) {
|
|
590
|
+
const a = e(n).first();
|
|
591
|
+
if (a.length && a.text().trim().length > 200) {
|
|
592
|
+
m = a.text();
|
|
569
593
|
break;
|
|
570
594
|
}
|
|
571
595
|
}
|
|
572
|
-
return
|
|
596
|
+
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 };
|
|
573
597
|
}
|
|
574
598
|
}, ce = {
|
|
575
599
|
name: "web_search",
|
|
@@ -579,20 +603,20 @@ const H = {
|
|
|
579
603
|
length: { type: "string", description: "Number of results to return", default: 5 }
|
|
580
604
|
},
|
|
581
605
|
fn: async (h) => {
|
|
582
|
-
const
|
|
606
|
+
const s = await fetch(`https://html.duckduckgo.com/html/?q=${encodeURIComponent(h.query)}`, {
|
|
583
607
|
headers: { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)", "Accept-Language": "en-US,en;q=0.9" }
|
|
584
|
-
}).then((
|
|
608
|
+
}).then((r) => r.text());
|
|
585
609
|
let e, t = /<a .*?href="(.+?)".+?<\/a>/g;
|
|
586
|
-
const
|
|
587
|
-
for (; (e = t.exec(
|
|
588
|
-
let
|
|
589
|
-
if (
|
|
610
|
+
const m = new O();
|
|
611
|
+
for (; (e = t.exec(s)) !== null; ) {
|
|
612
|
+
let r = /uddg=(.+)&?/.exec(decodeURIComponent(e[1]))?.[1];
|
|
613
|
+
if (r && (r = decodeURIComponent(r)), r && m.add(r), m.size >= (h.length || 5)) break;
|
|
590
614
|
}
|
|
591
|
-
return
|
|
615
|
+
return m;
|
|
592
616
|
}
|
|
593
617
|
};
|
|
594
618
|
export {
|
|
595
|
-
|
|
619
|
+
re as Ai,
|
|
596
620
|
W as Anthropic,
|
|
597
621
|
I as Audio,
|
|
598
622
|
H as CliTool,
|