@autorag/librarian 2.13.8 → 2.13.9
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/agent/agent.d.ts +10 -0
- package/dist/cli/commands/lite-find.d.ts +26 -0
- package/dist/cli/index.js +701 -10
- package/dist/core.js +56 -7
- package/dist/find/groups.d.ts +35 -0
- package/dist/find/index.d.ts +10 -0
- package/dist/find/relevance.d.ts +29 -0
- package/dist/find/run.d.ts +24 -0
- package/dist/find/text.d.ts +37 -0
- package/dist/find/types.d.ts +86 -0
- package/dist/index.js +56 -7
- package/dist/mcp/index.js +56 -7
- package/dist/minsync/channel-union.d.ts +23 -0
- package/dist/profiles.js +56 -7
- package/package.json +1 -1
package/dist/core.js
CHANGED
|
@@ -7618,6 +7618,34 @@ var init_memory = __esm(() => {
|
|
|
7618
7618
|
});
|
|
7619
7619
|
});
|
|
7620
7620
|
|
|
7621
|
+
// src/minsync/channel-union.ts
|
|
7622
|
+
function unionChannelResults(channels, options = {}) {
|
|
7623
|
+
const byChunk = new Map;
|
|
7624
|
+
const depth = Math.max(0, ...channels.map((channel) => channel.results.length));
|
|
7625
|
+
for (let rank = 0;rank < depth; rank++) {
|
|
7626
|
+
for (const channel of channels) {
|
|
7627
|
+
const result = channel.results[rank];
|
|
7628
|
+
if (result === undefined)
|
|
7629
|
+
continue;
|
|
7630
|
+
const key = `${result.source}\x00${result.id}`;
|
|
7631
|
+
const existing = byChunk.get(key);
|
|
7632
|
+
if (existing === undefined)
|
|
7633
|
+
byChunk.set(key, { result, ranks: { [channel.channel]: rank + 1 } });
|
|
7634
|
+
else
|
|
7635
|
+
existing.ranks[channel.channel] ??= rank + 1;
|
|
7636
|
+
}
|
|
7637
|
+
}
|
|
7638
|
+
const union = [...byChunk.values()].map(({ result, ranks }) => ({
|
|
7639
|
+
...result,
|
|
7640
|
+
metadata: {
|
|
7641
|
+
...result.metadata,
|
|
7642
|
+
minsyncChannels: Object.keys(ranks).sort(),
|
|
7643
|
+
minsyncChannelRanks: ranks
|
|
7644
|
+
}
|
|
7645
|
+
}));
|
|
7646
|
+
return options.limit === undefined ? union : union.slice(0, options.limit);
|
|
7647
|
+
}
|
|
7648
|
+
|
|
7621
7649
|
// src/minsync/local-embedder.ts
|
|
7622
7650
|
import { spawn as spawn11 } from "node:child_process";
|
|
7623
7651
|
import { request } from "node:http";
|
|
@@ -38721,6 +38749,7 @@ class AutoRAGAgent {
|
|
|
38721
38749
|
reranker;
|
|
38722
38750
|
rerankTopN;
|
|
38723
38751
|
minSyncMethod;
|
|
38752
|
+
minSyncSparseMethod;
|
|
38724
38753
|
minSyncEmbedderConfig;
|
|
38725
38754
|
minSyncBinaryPath;
|
|
38726
38755
|
jikjiClient;
|
|
@@ -38843,6 +38872,7 @@ class AutoRAGAgent {
|
|
|
38843
38872
|
...this.limits.recordCut === undefined ? {} : { recordCut: this.limits.recordCut }
|
|
38844
38873
|
};
|
|
38845
38874
|
this.minSyncMethod = new MinSyncVectorMethod(minSyncDefaults);
|
|
38875
|
+
this.minSyncSparseMethod = isSparseOnlyEmbedder(options.minSync?.embedder) ? undefined : new MinSyncVectorMethod({ ...minSyncDefaults, mode: "sparse" });
|
|
38846
38876
|
this.methodRegistry.register(new MinSyncHybridMethod(minSyncDefaults));
|
|
38847
38877
|
const registeredDatasourceIds = new Set;
|
|
38848
38878
|
for (const skill of this.datasourceSkills) {
|
|
@@ -40317,13 +40347,7 @@ Warning: the edited config did not validate, so the previous config was restored
|
|
|
40317
40347
|
${list.map((entry, index) => `[${index + 1}] ${entry}`).join(`
|
|
40318
40348
|
`)}`;
|
|
40319
40349
|
const perQuery = await Promise.all(searchQueries.map((searchQuery) => Promise.all([
|
|
40320
|
-
this.
|
|
40321
|
-
count: (found) => found.length
|
|
40322
|
-
}).catch((error) => {
|
|
40323
|
-
if (error instanceof MinSyncRequiredError)
|
|
40324
|
-
throw error;
|
|
40325
|
-
return [];
|
|
40326
|
-
}),
|
|
40350
|
+
this.prefetchMinSyncCandidates(searchQuery, retrieveOptions),
|
|
40327
40351
|
Promise.all(datasources.map((datasourceId) => this.searchProgress.track(`datasource:${datasourceId}`, `Datasource ${datasourceId}`, this.searchSingleDatasourceDocuments(datasourceId, searchQuery, {
|
|
40328
40352
|
topK: this.limits.prefetch.datasourceTopK,
|
|
40329
40353
|
scope: options.scope,
|
|
@@ -40438,6 +40462,30 @@ ${result.content.replace(/\s+/gu, " ")}`).join(`
|
|
|
40438
40462
|
|
|
40439
40463
|
`);
|
|
40440
40464
|
}
|
|
40465
|
+
async prefetchMinSyncCandidates(searchQuery, options) {
|
|
40466
|
+
const sparse = this.reranker === undefined ? undefined : this.minSyncSparseMethod;
|
|
40467
|
+
const run = (id, label, method) => this.searchProgress.track(id, label, method.retrieve(searchQuery, options), { count: (found) => found.length }).catch((error) => {
|
|
40468
|
+
if (error instanceof MinSyncRequiredError)
|
|
40469
|
+
throw error;
|
|
40470
|
+
if (sparse !== undefined) {
|
|
40471
|
+
this.routingDiagnostics.push({
|
|
40472
|
+
code: "retrieval-method-failed",
|
|
40473
|
+
severity: "warning",
|
|
40474
|
+
message: `MinSync ${id === "minsync" ? "dense" : "SPLADE"} search failed and its candidates were skipped; the other channel's were used: ${describeRetrievalError(error)}`,
|
|
40475
|
+
source: id
|
|
40476
|
+
});
|
|
40477
|
+
}
|
|
40478
|
+
return [];
|
|
40479
|
+
});
|
|
40480
|
+
const dense = run("minsync", "MinSync semantic search", this.minSyncMethod);
|
|
40481
|
+
if (sparse === undefined)
|
|
40482
|
+
return dense;
|
|
40483
|
+
const [vector, splade] = await Promise.all([dense, run("minsync-sparse", "MinSync SPLADE search", sparse)]);
|
|
40484
|
+
return unionChannelResults([
|
|
40485
|
+
{ channel: "vector", results: vector },
|
|
40486
|
+
{ channel: "sparse", results: splade }
|
|
40487
|
+
]);
|
|
40488
|
+
}
|
|
40441
40489
|
async prefetchWebSearch(searchQuery, signal) {
|
|
40442
40490
|
const web = this.webSearchOptions;
|
|
40443
40491
|
if (web === undefined)
|
|
@@ -41815,6 +41863,7 @@ var init_agent = __esm(() => {
|
|
|
41815
41863
|
init_merger();
|
|
41816
41864
|
init_rerank();
|
|
41817
41865
|
init_scope();
|
|
41866
|
+
init_skip();
|
|
41818
41867
|
init_search();
|
|
41819
41868
|
init_model_auth();
|
|
41820
41869
|
init_answer_citations();
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The group runners behind `autorag lite find`: one model-free search per
|
|
3
|
+
* surface — the platform file-name index, local document content, and each
|
|
4
|
+
* connected datasource, each asked on its own so a slow one never blocks the
|
|
5
|
+
* rest.
|
|
6
|
+
*/
|
|
7
|
+
import type { RetrievalResult } from "../retrieval/types.js";
|
|
8
|
+
import type { FindGroupDescriptor, FindGroupStatus, FindHit, FindJudge, FindRuntime } from "./types.js";
|
|
9
|
+
/** The result of running one group. */
|
|
10
|
+
export interface FindGroupOutcome {
|
|
11
|
+
readonly status: FindGroupStatus;
|
|
12
|
+
readonly hits: readonly FindHit[];
|
|
13
|
+
readonly error: string | null;
|
|
14
|
+
}
|
|
15
|
+
export interface FindGroupRunner {
|
|
16
|
+
readonly key: string;
|
|
17
|
+
readonly descriptor: FindGroupDescriptor;
|
|
18
|
+
run(): Promise<FindGroupOutcome>;
|
|
19
|
+
}
|
|
20
|
+
export interface BuildGroupRunnersOptions {
|
|
21
|
+
readonly query: string;
|
|
22
|
+
readonly topK: number;
|
|
23
|
+
readonly runtime: FindRuntime;
|
|
24
|
+
readonly judge?: FindJudge;
|
|
25
|
+
readonly platform?: NodeJS.Platform;
|
|
26
|
+
/** Whether the platform file-name index is enabled in config (default true). */
|
|
27
|
+
readonly fileSearchEnabled?: boolean;
|
|
28
|
+
/** Whether local semantic search is enabled in config (default true). */
|
|
29
|
+
readonly semanticEnabled?: boolean;
|
|
30
|
+
/** The enabled, registered datasources, in config order. */
|
|
31
|
+
readonly datasources?: readonly FindGroupDescriptor[];
|
|
32
|
+
}
|
|
33
|
+
/** A retrieval result as a datasource hit; exported for tests. */
|
|
34
|
+
export declare function sourceHit(result: RetrievalResult, query: string): FindHit;
|
|
35
|
+
export declare function buildGroupRunners(options: BuildGroupRunnersOptions): FindGroupRunner[];
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `autorag lite find`: a model-free whole search across the platform file-name
|
|
3
|
+
* index, local document content, and every connected datasource. Pure and
|
|
4
|
+
* injectable (runtime, judge); the CLI wires the real ones and prints NDJSON.
|
|
5
|
+
*/
|
|
6
|
+
export { type BuildGroupRunnersOptions, buildGroupRunners, type FindGroupOutcome, type FindGroupRunner, sourceHit, } from "./groups.js";
|
|
7
|
+
export { createRelevanceGate, JEV_RELEVANCE_THRESHOLD, type RelevanceCandidate, type RelevanceGateDeps, type SearchRelevanceGate, } from "./relevance.js";
|
|
8
|
+
export { type FindRunOptions, runFind } from "./run.js";
|
|
9
|
+
export { capContent, contentSnippet, escapeSearchRegex, everythingRegexQuery, fsearchRegexQuery, matchesQueryLexically, SEARCH_HIT_CONTENT_LIMIT, } from "./text.js";
|
|
10
|
+
export type { FindDoneLine, FindGroupDescriptor, FindGroupLine, FindGroupStatus, FindGroupsLine, FindHit, FindJudge, FindJudgment, FindKind, FindLine, FindRuntime, FindSearchAnswer, FindVerdict, } from "./types.js";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The relevance gate for the `content` and `source:*` groups.
|
|
3
|
+
*
|
|
4
|
+
* A deterministic lexical pass auto-keeps obvious hits; everything else goes to
|
|
5
|
+
* the decision model as batched `noul` questions, and only candidates with
|
|
6
|
+
* P(relevant) at or above {@link JEV_RELEVANCE_THRESHOLD} survive, ordered by
|
|
7
|
+
* probability. When no decision model can answer — the judge throws, or returns
|
|
8
|
+
* no numeric verdicts — every candidate is kept unfiltered (fail open).
|
|
9
|
+
*/
|
|
10
|
+
import type { FindJudge } from "./types.js";
|
|
11
|
+
/** Minimum judged probability `P(relevant)` required for a non-lexical hit to be shown. */
|
|
12
|
+
export declare const JEV_RELEVANCE_THRESHOLD = 0.5;
|
|
13
|
+
export interface RelevanceCandidate {
|
|
14
|
+
readonly id: string;
|
|
15
|
+
readonly sourceLabel: string;
|
|
16
|
+
readonly title?: string | null;
|
|
17
|
+
readonly subtitle?: string | null;
|
|
18
|
+
readonly content: string;
|
|
19
|
+
}
|
|
20
|
+
export interface SearchRelevanceGate {
|
|
21
|
+
filter<T extends RelevanceCandidate>(query: string, candidates: readonly T[]): Promise<T[]>;
|
|
22
|
+
}
|
|
23
|
+
export interface RelevanceGateDeps {
|
|
24
|
+
readonly judge: FindJudge;
|
|
25
|
+
readonly threshold?: number;
|
|
26
|
+
readonly maxQuestionsPerBatch?: number;
|
|
27
|
+
readonly maxConcurrentBatches?: number;
|
|
28
|
+
}
|
|
29
|
+
export declare function createRelevanceGate(deps: RelevanceGateDeps): SearchRelevanceGate;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orchestrates a whole search: emit the group list immediately, run every group
|
|
3
|
+
* in parallel, stream one group line per group in completion order, and finish
|
|
4
|
+
* with the done line. One group throwing never affects the others.
|
|
5
|
+
*/
|
|
6
|
+
import type { FindGroupDescriptor, FindJudge, FindLine, FindRuntime } from "./types.js";
|
|
7
|
+
export interface FindRunOptions {
|
|
8
|
+
readonly query: string;
|
|
9
|
+
readonly topK: number;
|
|
10
|
+
readonly runtime: FindRuntime;
|
|
11
|
+
readonly judge?: FindJudge;
|
|
12
|
+
readonly platform?: NodeJS.Platform;
|
|
13
|
+
readonly fileSearchEnabled?: boolean;
|
|
14
|
+
readonly semanticEnabled?: boolean;
|
|
15
|
+
/** The enabled, registered datasources, in config order. */
|
|
16
|
+
readonly datasources?: readonly FindGroupDescriptor[];
|
|
17
|
+
/** `--groups` narrowing: run only these keys. */
|
|
18
|
+
readonly groups?: readonly string[];
|
|
19
|
+
/** Emit the `groups` line before running. Pass false when the caller already announced it. */
|
|
20
|
+
readonly announce?: boolean;
|
|
21
|
+
readonly emit: (line: FindLine) => void;
|
|
22
|
+
readonly now?: () => number;
|
|
23
|
+
}
|
|
24
|
+
export declare function runFind(options: FindRunOptions): Promise<void>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text helpers for the whole-search groups: the relevance gate's deterministic
|
|
3
|
+
* lexical pass, the one-line snippet, and the platform file-name regexes.
|
|
4
|
+
*
|
|
5
|
+
* The lexical pass is ported from the Finder's `matchesQueryLexicallyOrApprox`
|
|
6
|
+
* with the approximate edit-distance step dropped, so it stays dependency-free.
|
|
7
|
+
*/
|
|
8
|
+
/** Passed text a hit carries, capped at this many characters. */
|
|
9
|
+
export declare const SEARCH_HIT_CONTENT_LIMIT = 2000;
|
|
10
|
+
/** The passage as one line, cut to {@link SNIPPET_LENGTH} around the first query term that appears in it. */
|
|
11
|
+
export declare function contentSnippet(text: string, query: string): string;
|
|
12
|
+
/** The passage flattened to one line and capped at {@link SEARCH_HIT_CONTENT_LIMIT} characters. */
|
|
13
|
+
export declare function capContent(text: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* Fast deterministic pass-through before Jev: exact query word match (NFKC
|
|
16
|
+
* case-folded, ASCII boundary-aware so `_`/`-`/Hangul neighbors work while
|
|
17
|
+
* partial stems like `art` in `start` do not, plus Korean postposition
|
|
18
|
+
* stripping like `비행기를` → `비행기`). The approximate edit-distance step the
|
|
19
|
+
* Finder had is intentionally dropped: this is dependency-free.
|
|
20
|
+
*/
|
|
21
|
+
export declare function matchesQueryLexically(query: string, candidateText: string): boolean;
|
|
22
|
+
/** Escape a literal query so an index's regex mode matches it as a substring. */
|
|
23
|
+
export declare function escapeSearchRegex(text: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* Regex for the fsearch index. macOS stores Hangul file names decomposed (NFD)
|
|
26
|
+
* while typed queries are composed (NFC), and the index compares bytes, so a
|
|
27
|
+
* name matches only when the query uses the same form. Sending both forms as an
|
|
28
|
+
* alternation finds either spelling; a query that has one form (ASCII) stays the
|
|
29
|
+
* plain escaped substring.
|
|
30
|
+
*/
|
|
31
|
+
export declare function fsearchRegexQuery(text: string): string;
|
|
32
|
+
/**
|
|
33
|
+
* Everything's `regex:` search function is the working regex surface on the
|
|
34
|
+
* bundled ES build. A bare space ends the function's argument, so spaces become
|
|
35
|
+
* `\x20` to keep a literal substring.
|
|
36
|
+
*/
|
|
37
|
+
export declare function everythingRegexQuery(text: string): string;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { Question } from "jev-use";
|
|
2
|
+
import type { EverythingSearchRequest, EverythingSearchResult } from "../everything/index.js";
|
|
3
|
+
import type { FSearchSearchRequest, FSearchSearchResult } from "../fsearch/index.js";
|
|
4
|
+
import type { RetrievalSelection } from "../retrieval/selection.js";
|
|
5
|
+
import type { RetrievalDiagnostic, RetrievalOptions, RetrievalResult, RetrievalUnsearchedSurface } from "../retrieval/types.js";
|
|
6
|
+
/** What a whole-search hit is: a file name, document content, or a datasource message. */
|
|
7
|
+
export type FindKind = "file" | "content" | "message";
|
|
8
|
+
/** One hit on one group, shared by the filename, content, and datasource surfaces. */
|
|
9
|
+
export interface FindHit {
|
|
10
|
+
readonly id: string;
|
|
11
|
+
readonly kind: FindKind;
|
|
12
|
+
readonly title: string;
|
|
13
|
+
readonly author: string | null;
|
|
14
|
+
readonly snippet: string;
|
|
15
|
+
/** Passed text, capped at {@link SEARCH_HIT_CONTENT_LIMIT} characters. */
|
|
16
|
+
readonly content: string;
|
|
17
|
+
/** Absolute local path for file and content hits; null for datasource messages. */
|
|
18
|
+
readonly path: string | null;
|
|
19
|
+
readonly line: number | null;
|
|
20
|
+
/** Epoch milliseconds, when the source states one. */
|
|
21
|
+
readonly timestamp: number | null;
|
|
22
|
+
readonly url: string | null;
|
|
23
|
+
readonly score: number | null;
|
|
24
|
+
}
|
|
25
|
+
/** The group list line, printed before any group runs. */
|
|
26
|
+
export interface FindGroupDescriptor {
|
|
27
|
+
readonly key: string;
|
|
28
|
+
readonly datasourceId?: string;
|
|
29
|
+
readonly datasourceType?: string;
|
|
30
|
+
}
|
|
31
|
+
/** `off` = the surface is disabled in config; `unavailable` = its index is not built. */
|
|
32
|
+
export type FindGroupStatus = "ready" | "error" | "off" | "unavailable";
|
|
33
|
+
export interface FindGroupsLine {
|
|
34
|
+
readonly type: "groups";
|
|
35
|
+
readonly groups: readonly FindGroupDescriptor[];
|
|
36
|
+
}
|
|
37
|
+
export interface FindGroupLine {
|
|
38
|
+
readonly type: "group";
|
|
39
|
+
readonly key: string;
|
|
40
|
+
readonly status: FindGroupStatus;
|
|
41
|
+
readonly hits: readonly FindHit[];
|
|
42
|
+
/** The group's own failure text verbatim, or null. */
|
|
43
|
+
readonly error: string | null;
|
|
44
|
+
readonly elapsedMs: number;
|
|
45
|
+
}
|
|
46
|
+
export interface FindDoneLine {
|
|
47
|
+
readonly type: "done";
|
|
48
|
+
readonly elapsedMs: number;
|
|
49
|
+
}
|
|
50
|
+
export type FindLine = FindGroupsLine | FindGroupLine | FindDoneLine;
|
|
51
|
+
/** The answer shape `searchSelected` returns; a structural subset of the retrieval engine's. */
|
|
52
|
+
export interface FindSearchAnswer {
|
|
53
|
+
readonly results: readonly RetrievalResult[];
|
|
54
|
+
readonly diagnostics: readonly RetrievalDiagnostic[];
|
|
55
|
+
readonly unsearched: readonly RetrievalUnsearchedSurface[];
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The model-free runtime a whole search drives. `AutoRAGLite` satisfies it
|
|
59
|
+
* structurally; tests inject a fake. Only these methods are touched, so a
|
|
60
|
+
* search never enters the model-backed agent loop.
|
|
61
|
+
*/
|
|
62
|
+
export interface FindRuntime {
|
|
63
|
+
listDatasources(): readonly {
|
|
64
|
+
readonly datasourceId: string;
|
|
65
|
+
}[];
|
|
66
|
+
searchSelected(query: string, selection?: RetrievalSelection, options?: RetrievalOptions): Promise<FindSearchAnswer>;
|
|
67
|
+
searchFsearch(request: FSearchSearchRequest): Promise<FSearchSearchResult>;
|
|
68
|
+
searchEverything(request: EverythingSearchRequest): Promise<EverythingSearchResult>;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* One batched `noul` judgment over relevance questions. The real implementation
|
|
72
|
+
* is `createDecisionModels(...).judge`; a thrown error means "no decision model
|
|
73
|
+
* can answer", and the gate fails open.
|
|
74
|
+
*/
|
|
75
|
+
export type FindJudge = (state: string, questions: Question[]) => Promise<FindJudgment>;
|
|
76
|
+
/** The verdicts the gate reads; a structural subset of `jev-use`'s `JudgeResult`. */
|
|
77
|
+
export interface FindJudgment {
|
|
78
|
+
readonly verdicts: readonly FindVerdict[];
|
|
79
|
+
}
|
|
80
|
+
export interface FindVerdict {
|
|
81
|
+
readonly id: string;
|
|
82
|
+
readonly type?: string;
|
|
83
|
+
readonly answer: number | string | null;
|
|
84
|
+
readonly confidence?: number;
|
|
85
|
+
readonly escalate?: boolean;
|
|
86
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -7618,6 +7618,34 @@ var init_memory = __esm(() => {
|
|
|
7618
7618
|
});
|
|
7619
7619
|
});
|
|
7620
7620
|
|
|
7621
|
+
// src/minsync/channel-union.ts
|
|
7622
|
+
function unionChannelResults(channels, options = {}) {
|
|
7623
|
+
const byChunk = new Map;
|
|
7624
|
+
const depth = Math.max(0, ...channels.map((channel) => channel.results.length));
|
|
7625
|
+
for (let rank = 0;rank < depth; rank++) {
|
|
7626
|
+
for (const channel of channels) {
|
|
7627
|
+
const result = channel.results[rank];
|
|
7628
|
+
if (result === undefined)
|
|
7629
|
+
continue;
|
|
7630
|
+
const key = `${result.source}\x00${result.id}`;
|
|
7631
|
+
const existing = byChunk.get(key);
|
|
7632
|
+
if (existing === undefined)
|
|
7633
|
+
byChunk.set(key, { result, ranks: { [channel.channel]: rank + 1 } });
|
|
7634
|
+
else
|
|
7635
|
+
existing.ranks[channel.channel] ??= rank + 1;
|
|
7636
|
+
}
|
|
7637
|
+
}
|
|
7638
|
+
const union = [...byChunk.values()].map(({ result, ranks }) => ({
|
|
7639
|
+
...result,
|
|
7640
|
+
metadata: {
|
|
7641
|
+
...result.metadata,
|
|
7642
|
+
minsyncChannels: Object.keys(ranks).sort(),
|
|
7643
|
+
minsyncChannelRanks: ranks
|
|
7644
|
+
}
|
|
7645
|
+
}));
|
|
7646
|
+
return options.limit === undefined ? union : union.slice(0, options.limit);
|
|
7647
|
+
}
|
|
7648
|
+
|
|
7621
7649
|
// src/minsync/local-embedder.ts
|
|
7622
7650
|
import { spawn as spawn11 } from "node:child_process";
|
|
7623
7651
|
import { request } from "node:http";
|
|
@@ -38721,6 +38749,7 @@ class AutoRAGAgent {
|
|
|
38721
38749
|
reranker;
|
|
38722
38750
|
rerankTopN;
|
|
38723
38751
|
minSyncMethod;
|
|
38752
|
+
minSyncSparseMethod;
|
|
38724
38753
|
minSyncEmbedderConfig;
|
|
38725
38754
|
minSyncBinaryPath;
|
|
38726
38755
|
jikjiClient;
|
|
@@ -38843,6 +38872,7 @@ class AutoRAGAgent {
|
|
|
38843
38872
|
...this.limits.recordCut === undefined ? {} : { recordCut: this.limits.recordCut }
|
|
38844
38873
|
};
|
|
38845
38874
|
this.minSyncMethod = new MinSyncVectorMethod(minSyncDefaults);
|
|
38875
|
+
this.minSyncSparseMethod = isSparseOnlyEmbedder(options.minSync?.embedder) ? undefined : new MinSyncVectorMethod({ ...minSyncDefaults, mode: "sparse" });
|
|
38846
38876
|
this.methodRegistry.register(new MinSyncHybridMethod(minSyncDefaults));
|
|
38847
38877
|
const registeredDatasourceIds = new Set;
|
|
38848
38878
|
for (const skill of this.datasourceSkills) {
|
|
@@ -40317,13 +40347,7 @@ Warning: the edited config did not validate, so the previous config was restored
|
|
|
40317
40347
|
${list.map((entry, index) => `[${index + 1}] ${entry}`).join(`
|
|
40318
40348
|
`)}`;
|
|
40319
40349
|
const perQuery = await Promise.all(searchQueries.map((searchQuery) => Promise.all([
|
|
40320
|
-
this.
|
|
40321
|
-
count: (found) => found.length
|
|
40322
|
-
}).catch((error) => {
|
|
40323
|
-
if (error instanceof MinSyncRequiredError)
|
|
40324
|
-
throw error;
|
|
40325
|
-
return [];
|
|
40326
|
-
}),
|
|
40350
|
+
this.prefetchMinSyncCandidates(searchQuery, retrieveOptions),
|
|
40327
40351
|
Promise.all(datasources.map((datasourceId) => this.searchProgress.track(`datasource:${datasourceId}`, `Datasource ${datasourceId}`, this.searchSingleDatasourceDocuments(datasourceId, searchQuery, {
|
|
40328
40352
|
topK: this.limits.prefetch.datasourceTopK,
|
|
40329
40353
|
scope: options.scope,
|
|
@@ -40438,6 +40462,30 @@ ${result.content.replace(/\s+/gu, " ")}`).join(`
|
|
|
40438
40462
|
|
|
40439
40463
|
`);
|
|
40440
40464
|
}
|
|
40465
|
+
async prefetchMinSyncCandidates(searchQuery, options) {
|
|
40466
|
+
const sparse = this.reranker === undefined ? undefined : this.minSyncSparseMethod;
|
|
40467
|
+
const run = (id, label, method) => this.searchProgress.track(id, label, method.retrieve(searchQuery, options), { count: (found) => found.length }).catch((error) => {
|
|
40468
|
+
if (error instanceof MinSyncRequiredError)
|
|
40469
|
+
throw error;
|
|
40470
|
+
if (sparse !== undefined) {
|
|
40471
|
+
this.routingDiagnostics.push({
|
|
40472
|
+
code: "retrieval-method-failed",
|
|
40473
|
+
severity: "warning",
|
|
40474
|
+
message: `MinSync ${id === "minsync" ? "dense" : "SPLADE"} search failed and its candidates were skipped; the other channel's were used: ${describeRetrievalError(error)}`,
|
|
40475
|
+
source: id
|
|
40476
|
+
});
|
|
40477
|
+
}
|
|
40478
|
+
return [];
|
|
40479
|
+
});
|
|
40480
|
+
const dense = run("minsync", "MinSync semantic search", this.minSyncMethod);
|
|
40481
|
+
if (sparse === undefined)
|
|
40482
|
+
return dense;
|
|
40483
|
+
const [vector, splade] = await Promise.all([dense, run("minsync-sparse", "MinSync SPLADE search", sparse)]);
|
|
40484
|
+
return unionChannelResults([
|
|
40485
|
+
{ channel: "vector", results: vector },
|
|
40486
|
+
{ channel: "sparse", results: splade }
|
|
40487
|
+
]);
|
|
40488
|
+
}
|
|
40441
40489
|
async prefetchWebSearch(searchQuery, signal) {
|
|
40442
40490
|
const web = this.webSearchOptions;
|
|
40443
40491
|
if (web === undefined)
|
|
@@ -41815,6 +41863,7 @@ var init_agent = __esm(() => {
|
|
|
41815
41863
|
init_merger();
|
|
41816
41864
|
init_rerank();
|
|
41817
41865
|
init_scope();
|
|
41866
|
+
init_skip();
|
|
41818
41867
|
init_search();
|
|
41819
41868
|
init_model_auth();
|
|
41820
41869
|
init_answer_citations();
|
package/dist/mcp/index.js
CHANGED
|
@@ -7619,6 +7619,34 @@ var init_memory = __esm(() => {
|
|
|
7619
7619
|
});
|
|
7620
7620
|
});
|
|
7621
7621
|
|
|
7622
|
+
// src/minsync/channel-union.ts
|
|
7623
|
+
function unionChannelResults(channels, options = {}) {
|
|
7624
|
+
const byChunk = new Map;
|
|
7625
|
+
const depth = Math.max(0, ...channels.map((channel) => channel.results.length));
|
|
7626
|
+
for (let rank = 0;rank < depth; rank++) {
|
|
7627
|
+
for (const channel of channels) {
|
|
7628
|
+
const result = channel.results[rank];
|
|
7629
|
+
if (result === undefined)
|
|
7630
|
+
continue;
|
|
7631
|
+
const key = `${result.source}\x00${result.id}`;
|
|
7632
|
+
const existing = byChunk.get(key);
|
|
7633
|
+
if (existing === undefined)
|
|
7634
|
+
byChunk.set(key, { result, ranks: { [channel.channel]: rank + 1 } });
|
|
7635
|
+
else
|
|
7636
|
+
existing.ranks[channel.channel] ??= rank + 1;
|
|
7637
|
+
}
|
|
7638
|
+
}
|
|
7639
|
+
const union = [...byChunk.values()].map(({ result, ranks }) => ({
|
|
7640
|
+
...result,
|
|
7641
|
+
metadata: {
|
|
7642
|
+
...result.metadata,
|
|
7643
|
+
minsyncChannels: Object.keys(ranks).sort(),
|
|
7644
|
+
minsyncChannelRanks: ranks
|
|
7645
|
+
}
|
|
7646
|
+
}));
|
|
7647
|
+
return options.limit === undefined ? union : union.slice(0, options.limit);
|
|
7648
|
+
}
|
|
7649
|
+
|
|
7622
7650
|
// src/minsync/local-embedder.ts
|
|
7623
7651
|
import { spawn as spawn11 } from "node:child_process";
|
|
7624
7652
|
import { request } from "node:http";
|
|
@@ -38722,6 +38750,7 @@ class AutoRAGAgent {
|
|
|
38722
38750
|
reranker;
|
|
38723
38751
|
rerankTopN;
|
|
38724
38752
|
minSyncMethod;
|
|
38753
|
+
minSyncSparseMethod;
|
|
38725
38754
|
minSyncEmbedderConfig;
|
|
38726
38755
|
minSyncBinaryPath;
|
|
38727
38756
|
jikjiClient;
|
|
@@ -38844,6 +38873,7 @@ class AutoRAGAgent {
|
|
|
38844
38873
|
...this.limits.recordCut === undefined ? {} : { recordCut: this.limits.recordCut }
|
|
38845
38874
|
};
|
|
38846
38875
|
this.minSyncMethod = new MinSyncVectorMethod(minSyncDefaults);
|
|
38876
|
+
this.minSyncSparseMethod = isSparseOnlyEmbedder(options.minSync?.embedder) ? undefined : new MinSyncVectorMethod({ ...minSyncDefaults, mode: "sparse" });
|
|
38847
38877
|
this.methodRegistry.register(new MinSyncHybridMethod(minSyncDefaults));
|
|
38848
38878
|
const registeredDatasourceIds = new Set;
|
|
38849
38879
|
for (const skill of this.datasourceSkills) {
|
|
@@ -40318,13 +40348,7 @@ Warning: the edited config did not validate, so the previous config was restored
|
|
|
40318
40348
|
${list.map((entry, index) => `[${index + 1}] ${entry}`).join(`
|
|
40319
40349
|
`)}`;
|
|
40320
40350
|
const perQuery = await Promise.all(searchQueries.map((searchQuery) => Promise.all([
|
|
40321
|
-
this.
|
|
40322
|
-
count: (found) => found.length
|
|
40323
|
-
}).catch((error) => {
|
|
40324
|
-
if (error instanceof MinSyncRequiredError)
|
|
40325
|
-
throw error;
|
|
40326
|
-
return [];
|
|
40327
|
-
}),
|
|
40351
|
+
this.prefetchMinSyncCandidates(searchQuery, retrieveOptions),
|
|
40328
40352
|
Promise.all(datasources.map((datasourceId) => this.searchProgress.track(`datasource:${datasourceId}`, `Datasource ${datasourceId}`, this.searchSingleDatasourceDocuments(datasourceId, searchQuery, {
|
|
40329
40353
|
topK: this.limits.prefetch.datasourceTopK,
|
|
40330
40354
|
scope: options.scope,
|
|
@@ -40439,6 +40463,30 @@ ${result.content.replace(/\s+/gu, " ")}`).join(`
|
|
|
40439
40463
|
|
|
40440
40464
|
`);
|
|
40441
40465
|
}
|
|
40466
|
+
async prefetchMinSyncCandidates(searchQuery, options) {
|
|
40467
|
+
const sparse = this.reranker === undefined ? undefined : this.minSyncSparseMethod;
|
|
40468
|
+
const run = (id, label, method) => this.searchProgress.track(id, label, method.retrieve(searchQuery, options), { count: (found) => found.length }).catch((error) => {
|
|
40469
|
+
if (error instanceof MinSyncRequiredError)
|
|
40470
|
+
throw error;
|
|
40471
|
+
if (sparse !== undefined) {
|
|
40472
|
+
this.routingDiagnostics.push({
|
|
40473
|
+
code: "retrieval-method-failed",
|
|
40474
|
+
severity: "warning",
|
|
40475
|
+
message: `MinSync ${id === "minsync" ? "dense" : "SPLADE"} search failed and its candidates were skipped; the other channel's were used: ${describeRetrievalError(error)}`,
|
|
40476
|
+
source: id
|
|
40477
|
+
});
|
|
40478
|
+
}
|
|
40479
|
+
return [];
|
|
40480
|
+
});
|
|
40481
|
+
const dense = run("minsync", "MinSync semantic search", this.minSyncMethod);
|
|
40482
|
+
if (sparse === undefined)
|
|
40483
|
+
return dense;
|
|
40484
|
+
const [vector, splade] = await Promise.all([dense, run("minsync-sparse", "MinSync SPLADE search", sparse)]);
|
|
40485
|
+
return unionChannelResults([
|
|
40486
|
+
{ channel: "vector", results: vector },
|
|
40487
|
+
{ channel: "sparse", results: splade }
|
|
40488
|
+
]);
|
|
40489
|
+
}
|
|
40442
40490
|
async prefetchWebSearch(searchQuery, signal) {
|
|
40443
40491
|
const web = this.webSearchOptions;
|
|
40444
40492
|
if (web === undefined)
|
|
@@ -41816,6 +41864,7 @@ var init_agent = __esm(() => {
|
|
|
41816
41864
|
init_merger();
|
|
41817
41865
|
init_rerank();
|
|
41818
41866
|
init_scope();
|
|
41867
|
+
init_skip();
|
|
41819
41868
|
init_search();
|
|
41820
41869
|
init_model_auth();
|
|
41821
41870
|
init_answer_citations();
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { RetrievalResult } from "../retrieval/types.js";
|
|
2
|
+
/** Ranked results of one MinSync query mode (`vector` or `sparse`), best first. */
|
|
3
|
+
export interface MinSyncChannelResults {
|
|
4
|
+
readonly channel: string;
|
|
5
|
+
readonly results: readonly RetrievalResult[];
|
|
6
|
+
}
|
|
7
|
+
export interface UnionChannelOptions {
|
|
8
|
+
/** Keep at most this many chunks after interleaving. Unset keeps the whole union. */
|
|
9
|
+
readonly limit?: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Union of independently ranked MinSync channels, built for a reranker that decides the final order.
|
|
13
|
+
*
|
|
14
|
+
* Channels are interleaved by rank (rank 1 of each channel, then rank 2, ...) so a limit never cuts one
|
|
15
|
+
* channel's head to keep the other's tail, and a chunk found by several channels appears once, at its
|
|
16
|
+
* best interleaved position. Scores are left as the first channel reported them: dense and SPLADE scores
|
|
17
|
+
* are not comparable and nothing here ranks across them. Each chunk records which channels found it and
|
|
18
|
+
* at what 1-based rank (`minsyncChannels`, `minsyncChannelRanks`).
|
|
19
|
+
*
|
|
20
|
+
* Identity is `source` + `id`, the same key the cross-method merger uses, so two chunks of one document
|
|
21
|
+
* stay distinct while the same chunk reached by two channels collapses.
|
|
22
|
+
*/
|
|
23
|
+
export declare function unionChannelResults(channels: readonly MinSyncChannelResults[], options?: UnionChannelOptions): RetrievalResult[];
|
package/dist/profiles.js
CHANGED
|
@@ -22145,6 +22145,34 @@ var init_memory = __esm(() => {
|
|
|
22145
22145
|
});
|
|
22146
22146
|
});
|
|
22147
22147
|
|
|
22148
|
+
// src/minsync/channel-union.ts
|
|
22149
|
+
function unionChannelResults(channels, options = {}) {
|
|
22150
|
+
const byChunk = new Map;
|
|
22151
|
+
const depth = Math.max(0, ...channels.map((channel) => channel.results.length));
|
|
22152
|
+
for (let rank = 0;rank < depth; rank++) {
|
|
22153
|
+
for (const channel of channels) {
|
|
22154
|
+
const result = channel.results[rank];
|
|
22155
|
+
if (result === undefined)
|
|
22156
|
+
continue;
|
|
22157
|
+
const key = `${result.source}\x00${result.id}`;
|
|
22158
|
+
const existing = byChunk.get(key);
|
|
22159
|
+
if (existing === undefined)
|
|
22160
|
+
byChunk.set(key, { result, ranks: { [channel.channel]: rank + 1 } });
|
|
22161
|
+
else
|
|
22162
|
+
existing.ranks[channel.channel] ??= rank + 1;
|
|
22163
|
+
}
|
|
22164
|
+
}
|
|
22165
|
+
const union = [...byChunk.values()].map(({ result, ranks }) => ({
|
|
22166
|
+
...result,
|
|
22167
|
+
metadata: {
|
|
22168
|
+
...result.metadata,
|
|
22169
|
+
minsyncChannels: Object.keys(ranks).sort(),
|
|
22170
|
+
minsyncChannelRanks: ranks
|
|
22171
|
+
}
|
|
22172
|
+
}));
|
|
22173
|
+
return options.limit === undefined ? union : union.slice(0, options.limit);
|
|
22174
|
+
}
|
|
22175
|
+
|
|
22148
22176
|
// src/retrieval/chunk-location.ts
|
|
22149
22177
|
function sectionLabel(line) {
|
|
22150
22178
|
const text = line.trim();
|
|
@@ -38663,6 +38691,7 @@ class AutoRAGAgent {
|
|
|
38663
38691
|
reranker;
|
|
38664
38692
|
rerankTopN;
|
|
38665
38693
|
minSyncMethod;
|
|
38694
|
+
minSyncSparseMethod;
|
|
38666
38695
|
minSyncEmbedderConfig;
|
|
38667
38696
|
minSyncBinaryPath;
|
|
38668
38697
|
jikjiClient;
|
|
@@ -38785,6 +38814,7 @@ class AutoRAGAgent {
|
|
|
38785
38814
|
...this.limits.recordCut === undefined ? {} : { recordCut: this.limits.recordCut }
|
|
38786
38815
|
};
|
|
38787
38816
|
this.minSyncMethod = new MinSyncVectorMethod(minSyncDefaults);
|
|
38817
|
+
this.minSyncSparseMethod = isSparseOnlyEmbedder(options.minSync?.embedder) ? undefined : new MinSyncVectorMethod({ ...minSyncDefaults, mode: "sparse" });
|
|
38788
38818
|
this.methodRegistry.register(new MinSyncHybridMethod(minSyncDefaults));
|
|
38789
38819
|
const registeredDatasourceIds = new Set;
|
|
38790
38820
|
for (const skill of this.datasourceSkills) {
|
|
@@ -40259,13 +40289,7 @@ Warning: the edited config did not validate, so the previous config was restored
|
|
|
40259
40289
|
${list.map((entry, index) => `[${index + 1}] ${entry}`).join(`
|
|
40260
40290
|
`)}`;
|
|
40261
40291
|
const perQuery = await Promise.all(searchQueries.map((searchQuery) => Promise.all([
|
|
40262
|
-
this.
|
|
40263
|
-
count: (found) => found.length
|
|
40264
|
-
}).catch((error) => {
|
|
40265
|
-
if (error instanceof MinSyncRequiredError)
|
|
40266
|
-
throw error;
|
|
40267
|
-
return [];
|
|
40268
|
-
}),
|
|
40292
|
+
this.prefetchMinSyncCandidates(searchQuery, retrieveOptions),
|
|
40269
40293
|
Promise.all(datasources.map((datasourceId) => this.searchProgress.track(`datasource:${datasourceId}`, `Datasource ${datasourceId}`, this.searchSingleDatasourceDocuments(datasourceId, searchQuery, {
|
|
40270
40294
|
topK: this.limits.prefetch.datasourceTopK,
|
|
40271
40295
|
scope: options.scope,
|
|
@@ -40380,6 +40404,30 @@ ${result.content.replace(/\s+/gu, " ")}`).join(`
|
|
|
40380
40404
|
|
|
40381
40405
|
`);
|
|
40382
40406
|
}
|
|
40407
|
+
async prefetchMinSyncCandidates(searchQuery, options) {
|
|
40408
|
+
const sparse = this.reranker === undefined ? undefined : this.minSyncSparseMethod;
|
|
40409
|
+
const run = (id, label, method) => this.searchProgress.track(id, label, method.retrieve(searchQuery, options), { count: (found) => found.length }).catch((error) => {
|
|
40410
|
+
if (error instanceof MinSyncRequiredError)
|
|
40411
|
+
throw error;
|
|
40412
|
+
if (sparse !== undefined) {
|
|
40413
|
+
this.routingDiagnostics.push({
|
|
40414
|
+
code: "retrieval-method-failed",
|
|
40415
|
+
severity: "warning",
|
|
40416
|
+
message: `MinSync ${id === "minsync" ? "dense" : "SPLADE"} search failed and its candidates were skipped; the other channel's were used: ${describeRetrievalError(error)}`,
|
|
40417
|
+
source: id
|
|
40418
|
+
});
|
|
40419
|
+
}
|
|
40420
|
+
return [];
|
|
40421
|
+
});
|
|
40422
|
+
const dense = run("minsync", "MinSync semantic search", this.minSyncMethod);
|
|
40423
|
+
if (sparse === undefined)
|
|
40424
|
+
return dense;
|
|
40425
|
+
const [vector, splade] = await Promise.all([dense, run("minsync-sparse", "MinSync SPLADE search", sparse)]);
|
|
40426
|
+
return unionChannelResults([
|
|
40427
|
+
{ channel: "vector", results: vector },
|
|
40428
|
+
{ channel: "sparse", results: splade }
|
|
40429
|
+
]);
|
|
40430
|
+
}
|
|
40383
40431
|
async prefetchWebSearch(searchQuery, signal) {
|
|
40384
40432
|
const web = this.webSearchOptions;
|
|
40385
40433
|
if (web === undefined)
|
|
@@ -41757,6 +41805,7 @@ var init_agent = __esm(() => {
|
|
|
41757
41805
|
init_merger();
|
|
41758
41806
|
init_rerank();
|
|
41759
41807
|
init_scope();
|
|
41808
|
+
init_skip();
|
|
41760
41809
|
init_search();
|
|
41761
41810
|
init_model_auth();
|
|
41762
41811
|
init_answer_citations();
|