@autorag/librarian 2.13.6 → 2.13.8

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.
Files changed (52) hide show
  1. package/README.md +15 -0
  2. package/dist/agent/agent.d.ts +35 -5
  3. package/dist/agent/calculator-tool.d.ts +39 -0
  4. package/dist/agent/dupey-tool.d.ts +0 -2
  5. package/dist/agent/evidence-ledger.d.ts +11 -0
  6. package/dist/agent/index-schedule.d.ts +45 -0
  7. package/dist/agent/index.d.ts +1 -0
  8. package/dist/agent/pipelined-sync.d.ts +34 -0
  9. package/dist/agent/result-render.d.ts +43 -0
  10. package/dist/agent/search-all-tool.d.ts +2 -1
  11. package/dist/agent/search-documents.d.ts +1 -1
  12. package/dist/agent/search-minsync-tool.d.ts +2 -1
  13. package/dist/agent/search-single-datasource-tool.d.ts +2 -1
  14. package/dist/agent/watch-refresh.d.ts +2 -0
  15. package/dist/calculator/civil.d.ts +27 -0
  16. package/dist/calculator/decimal.d.ts +47 -0
  17. package/dist/calculator/errors.d.ts +4 -0
  18. package/dist/calculator/expression.d.ts +11 -0
  19. package/dist/calculator/index.d.ts +17 -0
  20. package/dist/calculator/operations.d.ts +36 -0
  21. package/dist/calculator/temporal.d.ts +18 -0
  22. package/dist/calculator/timezone.d.ts +19 -0
  23. package/dist/cli/commands/duplicates.d.ts +2 -3
  24. package/dist/cli/commands/index.d.ts +1 -1
  25. package/dist/cli/commands/lite-refresh.d.ts +1 -1
  26. package/dist/cli/commands/refresh.d.ts +4 -0
  27. package/dist/cli/commands/watch.d.ts +2 -1
  28. package/dist/cli/index.js +3617 -1480
  29. package/dist/cli/signal-abort.d.ts +21 -0
  30. package/dist/core.d.ts +2 -0
  31. package/dist/core.js +4144 -2097
  32. package/dist/datasource/skills/factory.d.ts +4 -1
  33. package/dist/dupey/filter.d.ts +0 -2
  34. package/dist/dupey/index.d.ts +0 -1
  35. package/dist/dupey/version.d.ts +17 -0
  36. package/dist/index.js +4183 -2144
  37. package/dist/mcp/index.js +4182 -2142
  38. package/dist/mcp/server.d.ts +0 -3
  39. package/dist/minsync/installer.d.ts +1 -1
  40. package/dist/minsync/method.d.ts +7 -0
  41. package/dist/minsync/version.d.ts +3 -2
  42. package/dist/mirror/cloud-fingerprints.d.ts +6 -5
  43. package/dist/mirror/cloud.d.ts +9 -6
  44. package/dist/mirror/sync.d.ts +28 -15
  45. package/dist/profiles.js +3463 -1421
  46. package/dist/retrieval/engine.d.ts +7 -0
  47. package/dist/retrieval/merger.d.ts +6 -0
  48. package/dist/retrieval/record-cut.d.ts +101 -0
  49. package/package.json +1 -1
  50. package/skills/autorag/SKILL.md +28 -0
  51. package/skills/autorag-setup/SKILL.md +39 -0
  52. package/dist/dupey/scan-plan.d.ts +0 -23
@@ -10,6 +10,7 @@
10
10
  * after retrieval; local (non-datasource) methods and datasources without
11
11
  * source-scope support pass through untouched.
12
12
  */
13
+ import type { RecordCut } from "./record-cut.js";
13
14
  import { RetrievalMethodRegistry } from "./registry.js";
14
15
  import { type Reranker } from "./rerank.js";
15
16
  import { type RetrievalSelection } from "./selection.js";
@@ -45,6 +46,11 @@ export interface RetrievalEngineOptions {
45
46
  * Only applies when {@link reranker} is set. @default DEFAULT_RERANK_TOP_N
46
47
  */
47
48
  readonly rerankTopN?: number;
49
+ /**
50
+ * Cut the merged pool to `topK` spread across documents instead of by chunk, so one long document cannot
51
+ * take every slot (see {@link RecordCut}). Unset keeps the plain top-`topK` cut.
52
+ */
53
+ readonly recordCut?: RecordCut;
48
54
  }
49
55
  /**
50
56
  * Model-free retrieval pipeline.
@@ -76,6 +82,7 @@ export declare class RetrievalEngine {
76
82
  private readonly datasourceIdsProvider;
77
83
  private readonly reranker;
78
84
  private readonly rerankTopN;
85
+ private readonly recordCut;
79
86
  constructor(options?: RetrievalEngineOptions);
80
87
  /** The underlying method registry. Intentionally public for tooling. */
81
88
  getMethodRegistry(): RetrievalMethodRegistry;
@@ -1,3 +1,4 @@
1
+ import type { RecordCut } from "./record-cut.js";
1
2
  import type { RetrievalMethod, RetrievalOptions, RetrievalResult, RetrievalWithDiagnostics } from "./types.js";
2
3
  export interface MergeOptions {
3
4
  /**
@@ -8,6 +9,11 @@ export interface MergeOptions {
8
9
  topK: number;
9
10
  /** Retained for API compatibility; pure-duplicate removal is always enabled. */
10
11
  dedup: boolean;
12
+ /**
13
+ * When set, the ranked pool is cut to `topK` spread across documents (see {@link RecordCut}) instead of
14
+ * by chunk, so one long document cannot take every slot. Unset keeps the plain top-`topK` cut.
15
+ */
16
+ recordCut?: RecordCut;
11
17
  }
12
18
  /**
13
19
  * Cross-method evidence collator.
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Record cut: keep one long document from taking every slot of a ranked list.
3
+ *
4
+ * Hybrid retrieval ranks and cuts by passage. A long document that matches the
5
+ * query in dozens of passages fills the top N, and a document only the keyword
6
+ * leg found lands just past the cut. With the cut on, a search asks the backend
7
+ * for `depth` times what the caller wants (never past {@link OVERFETCH_ABSOLUTE_CAP})
8
+ * and keeps `limit` hits spread across documents: the best half with at most
9
+ * `perRecord` passages from any one document, then the best passage of each
10
+ * document not yet shown, then rank order for whatever room is left.
11
+ *
12
+ * The same module sizes what the model is *shown*: {@link planReadBudget} splits
13
+ * a result budget max-min fairly across the results of one tool call, so the
14
+ * first large result cannot spend all of it and leave the rest empty.
15
+ *
16
+ * The technique and its defaults follow PipesHub's `FUSION_RECORD_CUT`
17
+ * (`backend/python/app/modules/retrieval/record_cut.py`).
18
+ */
19
+ /** Most hits ever asked of a backend for the deeper fusion. */
20
+ export declare const OVERFETCH_ABSOLUTE_CAP = 300;
21
+ /** Past ten the cap binds for any limit above 30, and a small limit gains nothing from fusing hundreds of hits. */
22
+ export declare const MAX_FUSION_DEPTH = 10;
23
+ /** Bounds the per-record knobs: past the over-fetch cap a larger value changes nothing. */
24
+ export declare const MAX_PER_RECORD = 300;
25
+ /** Characters one search tool result may hold when the cut is on. */
26
+ export declare const DEFAULT_RESULT_CHARS = 60000;
27
+ /** Below this share a result is announced as not read instead of being clipped to a stub. */
28
+ export declare const DEFAULT_MIN_READ_CHARS = 2000;
29
+ export interface CutByRecordOptions<T> {
30
+ /** Most hits one record may hold in the head of the cut. */
31
+ readonly perRecord: number;
32
+ /** Slots at the end of `limit` kept for the best hit of each record not yet shown. */
33
+ readonly reserve: number;
34
+ /** The record a hit belongs to; undefined counts as a record of its own. */
35
+ readonly recordOf: (hit: T) => string | undefined;
36
+ }
37
+ /**
38
+ * Keep `limit` of `results` (best first), spread across records.
39
+ *
40
+ * The best `limit - reserve` results come first, at most `perRecord` from any
41
+ * one record. The reserve then goes to the best result of each record not yet
42
+ * represented, and anything left is filled in rank order, so a search confined
43
+ * to a few records still returns `limit`. Survivors keep their original order.
44
+ * A `limit` below 1 keeps everything.
45
+ */
46
+ export declare function cutByRecord<T>(results: readonly T[], limit: number, options: CutByRecordOptions<T>): T[];
47
+ /**
48
+ * What a caller may tune; every field is optional and clamped by {@link recordCutFromOptions}. The cut is on unless
49
+ * `enabled` is false.
50
+ */
51
+ export interface RecordCutOptions {
52
+ /** Set false to turn the record cut off. Default true. */
53
+ readonly enabled?: boolean;
54
+ /** Fusion depth: the backend is asked for `depth` x the limit. 1-10, default 3. */
55
+ readonly depth?: number;
56
+ /** Share of the limit kept for documents not yet shown. 0-1, default 0.5. */
57
+ readonly newRecordShare?: number;
58
+ /** One record keeps at most `max(perRecordFloor, limit / perRecordDivisor)` of the head. Default 4. */
59
+ readonly perRecordFloor?: number;
60
+ /** See `perRecordFloor`. Default 6. */
61
+ readonly perRecordDivisor?: number;
62
+ /** Characters one search tool result may hold. Default {@link DEFAULT_RESULT_CHARS}. */
63
+ readonly resultChars?: number;
64
+ /** Smallest share worth reading; a result below it is announced as not read. Default {@link DEFAULT_MIN_READ_CHARS}. */
65
+ readonly minReadChars?: number;
66
+ }
67
+ /** How deep a search fuses, how its hits are cut back by record, and how a result is budgeted. */
68
+ export declare class RecordCut {
69
+ readonly depth: number;
70
+ readonly newRecordShare: number;
71
+ readonly perRecordFloor: number;
72
+ readonly perRecordDivisor: number;
73
+ readonly resultChars: number;
74
+ readonly minReadChars: number;
75
+ constructor(options?: RecordCutOptions);
76
+ /** What to ask the backend for: `depth` x `limit`, within `cap`. The cap never lowers the caller's own limit. */
77
+ fusedLimit(limit: number, cap: number): number;
78
+ /** Passages one record may hold in the head of a cut to `limit` (8 at a limit of 50). */
79
+ perRecord(limit: number): number;
80
+ cut<T>(results: readonly T[], limit: number, recordOf: (hit: T) => string | undefined): T[];
81
+ }
82
+ /** The cut these options describe: on unless `enabled` is false, then undefined. Out-of-range tuning throws. */
83
+ export declare function recordCutFromOptions(options: RecordCutOptions | undefined): RecordCut | undefined;
84
+ /**
85
+ * Split `total` across results of the given sizes, max-min fair: a result that
86
+ * needs less than an equal split gets exactly what it needs and what it leaves
87
+ * is divided among the larger ones. Shares follow input order and never sum
88
+ * past `total`.
89
+ */
90
+ export declare function fairShares(sizes: readonly number[], total: number): number[];
91
+ /**
92
+ * Per-result character limits for one tool call, in input order (best first).
93
+ * `undefined` means the result is not read: reading it would push a better
94
+ * result's fair share under `minRead`.
95
+ *
96
+ * The largest prefix whose every member can still get `min(minRead, size)`
97
+ * characters is read, split max-min fairly, so the first large result cannot
98
+ * spend the budget and leave the rest empty. At least the first result is
99
+ * always read, with whatever the budget allows.
100
+ */
101
+ export declare function planReadBudget(sizes: readonly number[], total: number, minRead: number): (number | undefined)[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autorag/librarian",
3
- "version": "2.13.6",
3
+ "version": "2.13.8",
4
4
  "description": "Personal librarian agent for office work on the Pi framework, with pluggable retrieval methods",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -73,6 +73,34 @@ from it instead of probing. `"filee": { "enabled": false }` in `config.json` rem
73
73
  tools; `filee.timeoutMs` (default 30 minutes) and `filee.dataDir` (Filee's
74
74
  own settings and engines, `FILEE_DATA_DIR`) tune them.
75
75
 
76
+ ### Calculating numbers and dates
77
+
78
+ The `calculator` Agent tool does every calculation the model would otherwise
79
+ do in its head; the system prompt and each phase prompt (the fast answer
80
+ included) tell the model never to compute these itself. It is read-only,
81
+ in-process, and needs no configuration. Operations:
82
+
83
+ - `evaluate`: exact decimal arithmetic to 40 significant digits (`0.1 + 0.2`
84
+ is `0.3`), `+ - * / % ^ !`, percent and percent change, rounding, sums,
85
+ means, medians, standard deviations, roots, logs, and trig. `exact: false`
86
+ in the result means a step rounded; `exp`, `ln`, `log`, trig, and fractional
87
+ powers are accurate to about 15 digits, not 40. A bare `log(x)` is refused as
88
+ ambiguous; use `ln`, `log10`, or `log(x, base)`.
89
+ - `date_diff`, `date_add`, `date_info`: day counts, calendar spans
90
+ (`10 months, 7 days`), month-end clamping (Jan 31 + 1 month is Feb 28),
91
+ weekday, ISO week, and leap year. Dates read as `YYYY-MM-DD`, `YYYY/MM/DD`,
92
+ `YYYYMMDD`, `YYYY년 M월 D일`, ISO datetimes with an offset, epoch seconds,
93
+ `today`, or `now`.
94
+ - `business_days_between` and `add_business_days`: Monday to Friday minus a
95
+ `holidays` list. The tool knows no public holidays; pass them in.
96
+ - `now` and `convert_timezone`: the current time and DST-aware zone
97
+ conversion. An omitted `timeZone` means the host zone and the result says so;
98
+ a datetime with no offset needs its zone. A zone abbreviation other than
99
+ UTC, KST, and JST is refused, and the error names what to pass instead: PST,
100
+ PDT, MST, MDT, EST, and EDT are fixed offsets (PST is `-08:00` all year, so
101
+ it differs from `America/Los_Angeles` in summer), and CST, IST, and BST mean
102
+ several zones, so the user has to say which.
103
+
76
104
  ### Excluding local files from the index
77
105
 
78
106
  `"excludePaths"` in `config.json` lists files or folders (absolute, or relative
@@ -354,6 +354,45 @@ only when `rerank.model` names a model; then the pool is wide (Jikji 30, MinSync
354
354
  default) each source contributes its own top 10 to the first answer. An explicit
355
355
  value always wins.
356
356
 
357
+ ### Record cut (`limits.recordCut`)
358
+
359
+ On by default. A search that keeps only the top N *chunks* lets one long
360
+ document whose dozens of passages all match fill every slot while a document
361
+ only the keyword leg found falls past the cut. The record cut spreads the top N
362
+ across documents instead. Turn it off with `limits.recordCut.enabled: false`.
363
+ When on:
364
+
365
+ - MinSync and the merged search ask for `depth` times the requested count (3x,
366
+ never past 300 hits), then keep the requested count spread across documents:
367
+ the best half with at most `max(perRecordFloor, topK / perRecordDivisor)`
368
+ chunks per document (8 at `topK` 50), then the best chunk of each document not
369
+ shown yet (`newRecordShare`, default 0.5 of `topK`), then rank order.
370
+ - A chunk a search tool already showed during the same run is listed by id
371
+ without its text (`already shown earlier in this run`). A new run resends it.
372
+ - Each search tool result is held to `resultChars` (60000). The budget is split
373
+ max-min fairly across the chunks, so one huge chunk is cut with a note instead
374
+ of starving the rest; chunks whose share would fall under `minReadChars`
375
+ (2000) are listed as `not read` with their ids.
376
+
377
+ | Field | Default | Controls |
378
+ |---|---|---|
379
+ | `enabled` | true | Set `false` to turn the record cut off |
380
+ | `depth` | 3 | Fusion depth, 1-10 |
381
+ | `newRecordShare` | 0.5 | Share of `topK` reserved for documents not shown yet, 0-1 |
382
+ | `perRecordFloor` | 4 | Fewest chunks one document may keep in the head, 1-300 |
383
+ | `perRecordDivisor` | 6 | One document keeps at most `topK / perRecordDivisor` in the head, 1-300 |
384
+ | `resultChars` | 60000 | Characters one search tool result may hold |
385
+ | `minReadChars` | 2000 | Smallest share worth reading |
386
+
387
+ ```json
388
+ { "limits": { "recordCut": { "enabled": false } } }
389
+ ```
390
+
391
+ Measured on the maintainer's own corpus (297 sampled passages, a natural question
392
+ and a keyword query each, file-level gold, hybrid mode, top 10): nDCG@10 0.646 →
393
+ 0.656, MRR@10 0.588 → 0.600, Recall@10 0.825 → 0.830, and the top 10 holds 8.8
394
+ distinct files instead of 7.5. The Recall difference is within noise.
395
+
357
396
  Ingest caps are trusted connector options under `datasources.<name>.connector`
358
397
  and are never settable from model/tool arguments: `maxDocuments`,
359
398
  `maxItemsPerFeed`, and `maxContentChars` (RSS). `maxContentChars` defaults to
@@ -1,23 +0,0 @@
1
- import { type CloudFileProbe, type CloudRoot } from "../filesystem/cloud-placeholder.js";
2
- /** One `dupey scan` to run: the directory and the cloud or network folder names nested in it that must not be walked. */
3
- export interface DupeyScanTarget {
4
- readonly path: string;
5
- readonly excludeDirs: readonly string[];
6
- }
7
- export interface DupeyScanPlan {
8
- readonly targets: readonly DupeyScanTarget[];
9
- /** Roots inside a cloud or network folder: never scanned, because `dupey scan` opens every file's contents. */
10
- readonly skipped: readonly string[];
11
- }
12
- /**
13
- * The machine's cloud and network roots for the roots about to be scanned. A probe that is missing or fails
14
- * reads as no roots, never an error: an unknown root is scanned as before.
15
- */
16
- export declare function listCloudRoots(probe: CloudFileProbe | undefined, roots: readonly string[]): Promise<readonly CloudRoot[]>;
17
- /**
18
- * Which roots `dupey scan` may walk. A root inside a cloud or network folder is skipped whole and a cloud or
19
- * network folder nested under a root is excluded by name: `dupey scan` reads file contents, which re-downloads
20
- * a released placeholder (measured: a OneDrive folder of 1.1k placeholders ran past the 120 s scan timeout and
21
- * every read failed with ETIMEDOUT) and re-reads a network file over the wire.
22
- */
23
- export declare function planDupeyScans(roots: readonly string[], cloudRoots: readonly CloudRoot[]): DupeyScanPlan;