@mushinjs/plugin-knowledge 0.0.0-stage → 0.2.0-next.20261113103537
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/LICENSE +21 -0
- package/dist/index.d.ts +381 -0
- package/dist/index.js +1507 -0
- package/dist/index.js.map +1 -0
- package/dist/learn.d.ts +20 -0
- package/dist/learn.js +43 -0
- package/dist/learn.js.map +1 -0
- package/dist/node.js +129 -0
- package/dist/node.js.map +1 -0
- package/dist/search.d.ts +85 -0
- package/dist/search.js +218 -0
- package/dist/search.js.map +1 -0
- package/dist/text.d.ts +32 -0
- package/dist/text.js +183 -0
- package/dist/text.js.map +1 -0
- package/package.json +70 -3
- package/README.md +0 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Setobox
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import { IndexedChunk, SearchHit, SearchIndex, SearchOptions, dot, normalize, passages, tokenize } from "./search.js";
|
|
2
|
+
import { Candidate, ChatLineLike, LEARN_PROMPT, candidateText, parseCandidates, transcript } from "./learn.js";
|
|
3
|
+
import { ChunkOptions, DocFormat, FORMATS, TextChunk, chunkText, extractText, formatOf, htmlToText } from "./text.js";
|
|
4
|
+
import { Context, Service, z } from "@mushinjs/core";
|
|
5
|
+
import { BotRole } from "@mushinjs/plugin-bots";
|
|
6
|
+
declare module '@mushinjs/core' {
|
|
7
|
+
interface Context {
|
|
8
|
+
knowledge: Knowledge;
|
|
9
|
+
}
|
|
10
|
+
interface Events {
|
|
11
|
+
/** Bases, documents, chunks or candidates of a base changed. */
|
|
12
|
+
'knowledge/changed'(change: {
|
|
13
|
+
base: string;
|
|
14
|
+
}): void;
|
|
15
|
+
/** A document or a reindex is being processed. */
|
|
16
|
+
'knowledge/progress'(progress: KnowledgeProgress): void;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
declare module 'minato' {
|
|
20
|
+
interface Tables {
|
|
21
|
+
knowledge_base: KnowledgeBase;
|
|
22
|
+
knowledge_doc: KnowledgeDoc;
|
|
23
|
+
knowledge_chunk: ChunkRow;
|
|
24
|
+
knowledge_candidate: CandidateRow;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export interface LearnSettings {
|
|
28
|
+
/** Pick out candidate knowledge from the chats of the base's bots. */
|
|
29
|
+
enabled: boolean;
|
|
30
|
+
/** `group:<id>` or `private:<id>`; empty learns from every chat of the bots. */
|
|
31
|
+
channels: string[];
|
|
32
|
+
/** Learn once this many new messages arrived in a chat. */
|
|
33
|
+
every: number;
|
|
34
|
+
/** Add candidates straight to the base instead of waiting for review. */
|
|
35
|
+
autoAccept: boolean;
|
|
36
|
+
/** Most learning requests per day for this base. */
|
|
37
|
+
dailyLimit: number;
|
|
38
|
+
/** Provider and model for learning; empty uses the bot's, then the default. */
|
|
39
|
+
provider: string;
|
|
40
|
+
model: string;
|
|
41
|
+
}
|
|
42
|
+
export interface KnowledgeBase {
|
|
43
|
+
id: string;
|
|
44
|
+
name: string;
|
|
45
|
+
description: string;
|
|
46
|
+
/** Embedding provider and model; empty means keyword search only. */
|
|
47
|
+
provider: string;
|
|
48
|
+
model: string;
|
|
49
|
+
/** Length of the vectors; 0 until the base is embedded with its current model. */
|
|
50
|
+
dimension: number;
|
|
51
|
+
chunkSize: number;
|
|
52
|
+
chunkOverlap: number;
|
|
53
|
+
/** Defaults for searches that do not say. */
|
|
54
|
+
topK: number;
|
|
55
|
+
minScore: number;
|
|
56
|
+
/** Bots whose admins may use `/记住`, `/收录` and `/忘记`, and whose chats it learns from. */
|
|
57
|
+
bots: string[];
|
|
58
|
+
commands: {
|
|
59
|
+
enabled: boolean;
|
|
60
|
+
role: BotRole;
|
|
61
|
+
};
|
|
62
|
+
learn: LearnSettings;
|
|
63
|
+
createdAt: number;
|
|
64
|
+
updatedAt: number;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* `file`: an uploaded file. `url`: a web page, fetched again when reparsed. `text`: pasted text.
|
|
68
|
+
* `notes`: entries added one at a time (`/记住`, `/收录`, the dashboard). `learned`: accepted
|
|
69
|
+
* candidates from chats.
|
|
70
|
+
*/
|
|
71
|
+
export type DocKind = 'file' | 'url' | 'text' | 'notes' | 'learned';
|
|
72
|
+
export type DocStatus = 'pending' | 'parsing' | 'embedding' | 'ready' | 'error';
|
|
73
|
+
export interface KnowledgeDoc {
|
|
74
|
+
id: string;
|
|
75
|
+
base: string;
|
|
76
|
+
title: string;
|
|
77
|
+
kind: DocKind;
|
|
78
|
+
/** File name or URL. */
|
|
79
|
+
source: string;
|
|
80
|
+
format: string;
|
|
81
|
+
status: DocStatus;
|
|
82
|
+
error: string;
|
|
83
|
+
/** SHA-256 of the content, so the same file is not added twice. */
|
|
84
|
+
hash: string;
|
|
85
|
+
chars: number;
|
|
86
|
+
chunks: number;
|
|
87
|
+
createdAt: number;
|
|
88
|
+
updatedAt: number;
|
|
89
|
+
}
|
|
90
|
+
interface ChunkRow {
|
|
91
|
+
id: string;
|
|
92
|
+
base: string;
|
|
93
|
+
doc: string;
|
|
94
|
+
seq: number;
|
|
95
|
+
title: string;
|
|
96
|
+
text: string;
|
|
97
|
+
/** `Float32Array` bytes, normalized; empty when not embedded. */
|
|
98
|
+
vector: ArrayBuffer;
|
|
99
|
+
/** Changed by hand; reparsing the document replaces it anyway. */
|
|
100
|
+
edited: boolean;
|
|
101
|
+
/** For notes: who added it, where. */
|
|
102
|
+
meta: Record<string, any>;
|
|
103
|
+
createdAt: number;
|
|
104
|
+
updatedAt: number;
|
|
105
|
+
}
|
|
106
|
+
export type KnowledgeChunk = Omit<ChunkRow, 'vector'> & {
|
|
107
|
+
embedded: boolean;
|
|
108
|
+
};
|
|
109
|
+
export type CandidateStatus = 'pending' | 'accepted' | 'rejected';
|
|
110
|
+
interface CandidateRow {
|
|
111
|
+
id: number;
|
|
112
|
+
base: string;
|
|
113
|
+
bot: string;
|
|
114
|
+
channel: string;
|
|
115
|
+
question: string;
|
|
116
|
+
answer: string;
|
|
117
|
+
status: CandidateStatus;
|
|
118
|
+
/** The chunk an accepted candidate became. */
|
|
119
|
+
chunk: string;
|
|
120
|
+
createdAt: number;
|
|
121
|
+
updatedAt: number;
|
|
122
|
+
}
|
|
123
|
+
export type KnowledgeCandidate = CandidateRow;
|
|
124
|
+
export interface KnowledgeProgress {
|
|
125
|
+
base: string;
|
|
126
|
+
doc?: string;
|
|
127
|
+
/** `reindex` for a base being embedded again. */
|
|
128
|
+
stage: DocStatus | 'reindex' | 'done';
|
|
129
|
+
done: number;
|
|
130
|
+
total: number;
|
|
131
|
+
}
|
|
132
|
+
/** A search hit with the base and document it came from. */
|
|
133
|
+
export interface KnowledgeHit extends SearchHit {
|
|
134
|
+
base: string;
|
|
135
|
+
baseName: string;
|
|
136
|
+
docTitle: string;
|
|
137
|
+
}
|
|
138
|
+
export interface KnowledgeSearch {
|
|
139
|
+
hits: KnowledgeHit[];
|
|
140
|
+
/** Searched by meaning too (at least one base has an embedding model that answered). */
|
|
141
|
+
semantic: boolean;
|
|
142
|
+
/** Problems that made a base fall back to keywords, such as the embedding model failing. */
|
|
143
|
+
warnings: string[];
|
|
144
|
+
}
|
|
145
|
+
/** A hit as a reply's provenance and a run's state keep it. */
|
|
146
|
+
export interface KnowledgeRef {
|
|
147
|
+
base: string;
|
|
148
|
+
doc: string;
|
|
149
|
+
chunk: string;
|
|
150
|
+
title: string;
|
|
151
|
+
}
|
|
152
|
+
declare const baseFields: {
|
|
153
|
+
name: z.ZodString;
|
|
154
|
+
description: z.ZodOptional<z.ZodString>;
|
|
155
|
+
provider: z.ZodOptional<z.ZodString>;
|
|
156
|
+
model: z.ZodOptional<z.ZodString>;
|
|
157
|
+
chunkSize: z.ZodOptional<z.ZodNumber>;
|
|
158
|
+
chunkOverlap: z.ZodOptional<z.ZodNumber>;
|
|
159
|
+
topK: z.ZodOptional<z.ZodNumber>;
|
|
160
|
+
minScore: z.ZodOptional<z.ZodNumber>;
|
|
161
|
+
bots: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
162
|
+
commands: z.ZodOptional<z.ZodObject<{
|
|
163
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
164
|
+
role: z.ZodOptional<z.ZodEnum<{
|
|
165
|
+
master: "master";
|
|
166
|
+
admin: "admin";
|
|
167
|
+
user: "user";
|
|
168
|
+
}>>;
|
|
169
|
+
}, z.core.$strip>>;
|
|
170
|
+
learn: z.ZodOptional<z.ZodObject<{
|
|
171
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
172
|
+
channels: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
173
|
+
every: z.ZodOptional<z.ZodNumber>;
|
|
174
|
+
autoAccept: z.ZodOptional<z.ZodBoolean>;
|
|
175
|
+
dailyLimit: z.ZodOptional<z.ZodNumber>;
|
|
176
|
+
provider: z.ZodOptional<z.ZodString>;
|
|
177
|
+
model: z.ZodOptional<z.ZodString>;
|
|
178
|
+
}, z.core.$strip>>;
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* Knowledge bases: documents split into chunks, searched by keywords and (with an embedding
|
|
182
|
+
* model) by meaning. Flows search them with a node, the Agent with `kb.search`, admins add to
|
|
183
|
+
* them from chat, and they can learn candidate knowledge from chats for review.
|
|
184
|
+
*/
|
|
185
|
+
export declare class Knowledge extends Service {
|
|
186
|
+
static inject: string[];
|
|
187
|
+
private home;
|
|
188
|
+
private indexes;
|
|
189
|
+
private queue;
|
|
190
|
+
private abort;
|
|
191
|
+
/** Base id → `chat key` → messages since the last learning. */
|
|
192
|
+
private counters;
|
|
193
|
+
private learning;
|
|
194
|
+
/** Chat key → when it was last learned from, so the same messages are not read twice. */
|
|
195
|
+
private learnState;
|
|
196
|
+
private usage;
|
|
197
|
+
constructor(ctx: Context);
|
|
198
|
+
private get logger();
|
|
199
|
+
private get db();
|
|
200
|
+
[Service.init](): AsyncGenerator<import("cordis").Disposable<Promise<void>> | (() => boolean), void, unknown>;
|
|
201
|
+
list(): Promise<import("minato").FlatPick<KnowledgeBase, any>[]>;
|
|
202
|
+
get(id: string): Promise<KnowledgeBase>;
|
|
203
|
+
/** Bases with their document, chunk and candidate counts, for the list page. */
|
|
204
|
+
summaries(): Promise<{
|
|
205
|
+
docs: number;
|
|
206
|
+
chunks: number;
|
|
207
|
+
embedded: number;
|
|
208
|
+
busy: boolean;
|
|
209
|
+
candidates: number;
|
|
210
|
+
bots: string[];
|
|
211
|
+
model: string;
|
|
212
|
+
id: string;
|
|
213
|
+
description: string;
|
|
214
|
+
createdAt: number;
|
|
215
|
+
name: string;
|
|
216
|
+
provider: string;
|
|
217
|
+
dimension: number;
|
|
218
|
+
chunkSize: number;
|
|
219
|
+
chunkOverlap: number;
|
|
220
|
+
topK: number;
|
|
221
|
+
minScore: number;
|
|
222
|
+
commands: {
|
|
223
|
+
enabled: boolean;
|
|
224
|
+
role: BotRole;
|
|
225
|
+
};
|
|
226
|
+
learn: LearnSettings;
|
|
227
|
+
updatedAt: number;
|
|
228
|
+
}[]>;
|
|
229
|
+
/** Embeds a probe text to learn the model's vector length; fails with a readable error. */
|
|
230
|
+
private probe;
|
|
231
|
+
create(input: z.infer<z.ZodObject<typeof baseFields>>): Promise<KnowledgeBase>;
|
|
232
|
+
update(id: string, patch: Partial<z.infer<z.ZodObject<typeof baseFields>>>): Promise<KnowledgeBase>;
|
|
233
|
+
remove(id: string): Promise<{
|
|
234
|
+
ok: boolean;
|
|
235
|
+
}>;
|
|
236
|
+
private changed;
|
|
237
|
+
private progress;
|
|
238
|
+
private fileDir;
|
|
239
|
+
private filePath;
|
|
240
|
+
docs(base: string): Promise<import("minato").FlatPick<KnowledgeDoc, any>[]>;
|
|
241
|
+
doc(id: string): Promise<KnowledgeDoc>;
|
|
242
|
+
/**
|
|
243
|
+
* Adds a document: a file (`data` in base64 with its `name`), pasted `text`, or a `url` to fetch.
|
|
244
|
+
* It is parsed, split and embedded in the background; `knowledge/progress` reports how it goes.
|
|
245
|
+
*/
|
|
246
|
+
addDoc(input: {
|
|
247
|
+
base: string;
|
|
248
|
+
name?: string;
|
|
249
|
+
text?: string;
|
|
250
|
+
data?: string;
|
|
251
|
+
url?: string;
|
|
252
|
+
}): Promise<KnowledgeDoc>;
|
|
253
|
+
private fetchPage;
|
|
254
|
+
removeDoc(id: string): Promise<{
|
|
255
|
+
ok: boolean;
|
|
256
|
+
}>;
|
|
257
|
+
/** Parses a document again from its file (a web page is fetched again); edits to its chunks are lost. */
|
|
258
|
+
reparse(id: string): Promise<{
|
|
259
|
+
ok: boolean;
|
|
260
|
+
}>;
|
|
261
|
+
private enqueue;
|
|
262
|
+
private setStatus;
|
|
263
|
+
/** Embeds texts with a base's model, `onBatch` after each batch; nothing for keyword-only bases. */
|
|
264
|
+
private embedTexts;
|
|
265
|
+
/** What a chunk's embedding reads: its title and text. */
|
|
266
|
+
private embedText;
|
|
267
|
+
private process;
|
|
268
|
+
/** Embeds every chunk of a base again with its current model, e.g. after the model changed. */
|
|
269
|
+
reindex(id: string): Promise<unknown>;
|
|
270
|
+
chunks(input: {
|
|
271
|
+
base: string;
|
|
272
|
+
doc?: string;
|
|
273
|
+
offset?: number;
|
|
274
|
+
limit?: number;
|
|
275
|
+
query?: string;
|
|
276
|
+
}): Promise<{
|
|
277
|
+
total: number;
|
|
278
|
+
chunks: KnowledgeChunk[];
|
|
279
|
+
}>;
|
|
280
|
+
private chunkRow;
|
|
281
|
+
updateChunk(id: string, patch: {
|
|
282
|
+
title?: string;
|
|
283
|
+
text?: string;
|
|
284
|
+
}): Promise<KnowledgeChunk>;
|
|
285
|
+
removeChunk(id: string): Promise<{
|
|
286
|
+
ok: boolean;
|
|
287
|
+
}>;
|
|
288
|
+
/** Joins a chunk with the one after it in the same document. */
|
|
289
|
+
mergeChunk(id: string): Promise<KnowledgeChunk>;
|
|
290
|
+
private recount;
|
|
291
|
+
/** The document of single entries of a kind, made the first time one is added. */
|
|
292
|
+
private entriesDoc;
|
|
293
|
+
/** Adds one entry, such as a `/记住` note or an accepted candidate, as a chunk of its own. */
|
|
294
|
+
addEntry(baseId: string, entry: {
|
|
295
|
+
text: string;
|
|
296
|
+
title?: string;
|
|
297
|
+
kind?: 'notes' | 'learned';
|
|
298
|
+
meta?: Record<string, any>;
|
|
299
|
+
}): Promise<KnowledgeChunk>;
|
|
300
|
+
/** Removes notes that contain the text; returns how many. */
|
|
301
|
+
forget(baseId: string, text: string): Promise<number>;
|
|
302
|
+
private index;
|
|
303
|
+
/**
|
|
304
|
+
* Searches bases (every one when none are given) by keywords and, where the base has an
|
|
305
|
+
* embedding model, by meaning. A failing embedding model falls back to keywords with a warning.
|
|
306
|
+
*/
|
|
307
|
+
search(input: {
|
|
308
|
+
bases?: string[];
|
|
309
|
+
query: string;
|
|
310
|
+
topK?: number;
|
|
311
|
+
minScore?: number;
|
|
312
|
+
signal?: AbortSignal;
|
|
313
|
+
}): Promise<KnowledgeSearch>;
|
|
314
|
+
/** Chunks and links between similar ones, for the similarity map. */
|
|
315
|
+
graph(input: {
|
|
316
|
+
base: string;
|
|
317
|
+
neighbors?: number;
|
|
318
|
+
minSimilarity?: number;
|
|
319
|
+
limit?: number;
|
|
320
|
+
}): Promise<{
|
|
321
|
+
byVector: boolean;
|
|
322
|
+
total: number;
|
|
323
|
+
nodes: {
|
|
324
|
+
id: string;
|
|
325
|
+
doc: string;
|
|
326
|
+
docTitle: string;
|
|
327
|
+
title: string;
|
|
328
|
+
text: string;
|
|
329
|
+
}[];
|
|
330
|
+
edges: {
|
|
331
|
+
source: string;
|
|
332
|
+
target: string;
|
|
333
|
+
similarity: number;
|
|
334
|
+
}[];
|
|
335
|
+
}>;
|
|
336
|
+
/** Flows that ask an LLM, for "use in a flow", with whether they already search this base. */
|
|
337
|
+
flowChoices(base?: string): {
|
|
338
|
+
id: string;
|
|
339
|
+
name: string;
|
|
340
|
+
bots: string[];
|
|
341
|
+
llmChat: boolean;
|
|
342
|
+
uses: boolean;
|
|
343
|
+
}[];
|
|
344
|
+
/**
|
|
345
|
+
* Puts a knowledge search in front of a flow's first "Ask the LLM" node and passes its passages
|
|
346
|
+
* to it; a flow that already has a search node gets these bases added to it.
|
|
347
|
+
*/
|
|
348
|
+
useInFlow(flowId: string, bases: string[]): Promise<{
|
|
349
|
+
flow: string;
|
|
350
|
+
node: string;
|
|
351
|
+
added: boolean;
|
|
352
|
+
}>;
|
|
353
|
+
/** Bases a bot may add to from chat, oldest first. */
|
|
354
|
+
private basesFor;
|
|
355
|
+
private defineCommands;
|
|
356
|
+
private metaOf;
|
|
357
|
+
/** The base a chat command writes to, after checking the sender may use it; tells them when not. */
|
|
358
|
+
private allowed;
|
|
359
|
+
private onMessage;
|
|
360
|
+
private useQuota;
|
|
361
|
+
/**
|
|
362
|
+
* Reads a chat's messages since it was last learned from and asks the LLM for knowledge worth
|
|
363
|
+
* keeping. What it finds waits for review, or is added straight away with auto-accept.
|
|
364
|
+
*/
|
|
365
|
+
learn(baseId: string, botId: string, chat: string): Promise<{
|
|
366
|
+
candidates: KnowledgeCandidate[];
|
|
367
|
+
skipped?: string;
|
|
368
|
+
}>;
|
|
369
|
+
/** Leaves out candidates already waiting for review or already known to the base. */
|
|
370
|
+
private newCandidates;
|
|
371
|
+
candidates(base: string, status?: CandidateStatus): Promise<import("minato").FlatPick<CandidateRow, any>[]>;
|
|
372
|
+
private candidate;
|
|
373
|
+
/** Adds a candidate (as edited) to the base's "learned from chats" document. */
|
|
374
|
+
accept(id: number, patch?: {
|
|
375
|
+
question?: string;
|
|
376
|
+
answer?: string;
|
|
377
|
+
}): Promise<CandidateRow>;
|
|
378
|
+
reject(id: number): Promise<CandidateRow>;
|
|
379
|
+
private defineActions;
|
|
380
|
+
}
|
|
381
|
+
export { Candidate, ChatLineLike, ChunkOptions, DocFormat, FORMATS, IndexedChunk, Knowledge as default, LEARN_PROMPT, SearchHit, SearchIndex, SearchOptions, TextChunk, candidateText, chunkText, dot, extractText, formatOf, htmlToText, normalize, parseCandidates, passages, tokenize, transcript };
|