agentfootprint 8.9.0 → 8.10.0
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/AGENTS.md +2 -0
- package/CLAUDE.md +4 -0
- package/bin/agentfootprint-index.mjs +192 -0
- package/dist/doors/rag.js +66 -0
- package/dist/doors/rag.js.map +1 -0
- package/dist/esm/doors/rag.d.ts +49 -0
- package/dist/esm/doors/rag.js +50 -0
- package/dist/esm/doors/rag.js.map +1 -0
- package/dist/esm/rag/hash.d.ts +2 -0
- package/dist/esm/rag/hash.js +22 -0
- package/dist/esm/rag/hash.js.map +1 -0
- package/dist/esm/rag/index.d.ts +17 -0
- package/dist/esm/rag/index.js +17 -0
- package/dist/esm/rag/index.js.map +1 -0
- package/dist/esm/rag/indexCorpus.d.ts +102 -0
- package/dist/esm/rag/indexCorpus.js +339 -0
- package/dist/esm/rag/indexCorpus.js.map +1 -0
- package/dist/esm/rag/indexFolder.d.ts +40 -0
- package/dist/esm/rag/indexFolder.js +42 -0
- package/dist/esm/rag/indexFolder.js.map +1 -0
- package/dist/esm/rag/loadDocuments.d.ts +34 -0
- package/dist/esm/rag/loadDocuments.js +160 -0
- package/dist/esm/rag/loadDocuments.js.map +1 -0
- package/dist/esm/rag/loaders/html.d.ts +42 -0
- package/dist/esm/rag/loaders/html.js +48 -0
- package/dist/esm/rag/loaders/html.js.map +1 -0
- package/dist/esm/rag/loaders/index.d.ts +30 -0
- package/dist/esm/rag/loaders/index.js +24 -0
- package/dist/esm/rag/loaders/index.js.map +1 -0
- package/dist/esm/rag/loaders/markdown.d.ts +27 -0
- package/dist/esm/rag/loaders/markdown.js +12 -0
- package/dist/esm/rag/loaders/markdown.js.map +1 -0
- package/dist/esm/rag/loaders/mock.d.ts +28 -0
- package/dist/esm/rag/loaders/mock.js +14 -0
- package/dist/esm/rag/loaders/mock.js.map +1 -0
- package/dist/esm/rag/loaders/pdf.d.ts +39 -0
- package/dist/esm/rag/loaders/pdf.js +90 -0
- package/dist/esm/rag/loaders/pdf.js.map +1 -0
- package/dist/esm/rag/loaders/text.d.ts +23 -0
- package/dist/esm/rag/loaders/text.js +21 -0
- package/dist/esm/rag/loaders/text.js.map +1 -0
- package/dist/esm/rag/splitDocuments.d.ts +21 -0
- package/dist/esm/rag/splitDocuments.js +65 -0
- package/dist/esm/rag/splitDocuments.js.map +1 -0
- package/dist/esm/rag/splitters/byHeading.d.ts +36 -0
- package/dist/esm/rag/splitters/byHeading.js +66 -0
- package/dist/esm/rag/splitters/byHeading.js.map +1 -0
- package/dist/esm/rag/splitters/byParagraph.d.ts +24 -0
- package/dist/esm/rag/splitters/byParagraph.js +16 -0
- package/dist/esm/rag/splitters/byParagraph.js.map +1 -0
- package/dist/esm/rag/splitters/constants.d.ts +19 -0
- package/dist/esm/rag/splitters/constants.js +20 -0
- package/dist/esm/rag/splitters/constants.js.map +1 -0
- package/dist/esm/rag/splitters/fixedWithOverlap.d.ts +31 -0
- package/dist/esm/rag/splitters/fixedWithOverlap.js +34 -0
- package/dist/esm/rag/splitters/fixedWithOverlap.js.map +1 -0
- package/dist/esm/rag/splitters/index.d.ts +50 -0
- package/dist/esm/rag/splitters/index.js +51 -0
- package/dist/esm/rag/splitters/index.js.map +1 -0
- package/dist/esm/rag/splitters/shared.d.ts +87 -0
- package/dist/esm/rag/splitters/shared.js +188 -0
- package/dist/esm/rag/splitters/shared.js.map +1 -0
- package/dist/esm/rag/splitters/wholeDocument.d.ts +16 -0
- package/dist/esm/rag/splitters/wholeDocument.js +10 -0
- package/dist/esm/rag/splitters/wholeDocument.js.map +1 -0
- package/dist/esm/rag/types.d.ts +200 -0
- package/dist/esm/rag/types.js +20 -0
- package/dist/esm/rag/types.js.map +1 -0
- package/dist/rag/hash.js +26 -0
- package/dist/rag/hash.js.map +1 -0
- package/dist/rag/index.js +40 -0
- package/dist/rag/index.js.map +1 -0
- package/dist/rag/indexCorpus.js +344 -0
- package/dist/rag/indexCorpus.js.map +1 -0
- package/dist/rag/indexFolder.js +46 -0
- package/dist/rag/indexFolder.js.map +1 -0
- package/dist/rag/loadDocuments.js +164 -0
- package/dist/rag/loadDocuments.js.map +1 -0
- package/dist/rag/loaders/html.js +53 -0
- package/dist/rag/loaders/html.js.map +1 -0
- package/dist/rag/loaders/index.js +33 -0
- package/dist/rag/loaders/index.js.map +1 -0
- package/dist/rag/loaders/markdown.js +16 -0
- package/dist/rag/loaders/markdown.js.map +1 -0
- package/dist/rag/loaders/mock.js +18 -0
- package/dist/rag/loaders/mock.js.map +1 -0
- package/dist/rag/loaders/pdf.js +118 -0
- package/dist/rag/loaders/pdf.js.map +1 -0
- package/dist/rag/loaders/text.js +26 -0
- package/dist/rag/loaders/text.js.map +1 -0
- package/dist/rag/splitDocuments.js +69 -0
- package/dist/rag/splitDocuments.js.map +1 -0
- package/dist/rag/splitters/byHeading.js +70 -0
- package/dist/rag/splitters/byHeading.js.map +1 -0
- package/dist/rag/splitters/byParagraph.js +20 -0
- package/dist/rag/splitters/byParagraph.js.map +1 -0
- package/dist/rag/splitters/constants.js +23 -0
- package/dist/rag/splitters/constants.js.map +1 -0
- package/dist/rag/splitters/fixedWithOverlap.js +38 -0
- package/dist/rag/splitters/fixedWithOverlap.js.map +1 -0
- package/dist/rag/splitters/index.js +60 -0
- package/dist/rag/splitters/index.js.map +1 -0
- package/dist/rag/splitters/shared.js +199 -0
- package/dist/rag/splitters/shared.js.map +1 -0
- package/dist/rag/splitters/wholeDocument.js +14 -0
- package/dist/rag/splitters/wholeDocument.js.map +1 -0
- package/dist/rag/types.js +21 -0
- package/dist/rag/types.js.map +1 -0
- package/dist/types/doors/rag.d.ts +50 -0
- package/dist/types/doors/rag.d.ts.map +1 -0
- package/dist/types/rag/hash.d.ts +3 -0
- package/dist/types/rag/hash.d.ts.map +1 -0
- package/dist/types/rag/index.d.ts +18 -0
- package/dist/types/rag/index.d.ts.map +1 -0
- package/dist/types/rag/indexCorpus.d.ts +103 -0
- package/dist/types/rag/indexCorpus.d.ts.map +1 -0
- package/dist/types/rag/indexFolder.d.ts +41 -0
- package/dist/types/rag/indexFolder.d.ts.map +1 -0
- package/dist/types/rag/loadDocuments.d.ts +35 -0
- package/dist/types/rag/loadDocuments.d.ts.map +1 -0
- package/dist/types/rag/loaders/html.d.ts +43 -0
- package/dist/types/rag/loaders/html.d.ts.map +1 -0
- package/dist/types/rag/loaders/index.d.ts +31 -0
- package/dist/types/rag/loaders/index.d.ts.map +1 -0
- package/dist/types/rag/loaders/markdown.d.ts +28 -0
- package/dist/types/rag/loaders/markdown.d.ts.map +1 -0
- package/dist/types/rag/loaders/mock.d.ts +29 -0
- package/dist/types/rag/loaders/mock.d.ts.map +1 -0
- package/dist/types/rag/loaders/pdf.d.ts +40 -0
- package/dist/types/rag/loaders/pdf.d.ts.map +1 -0
- package/dist/types/rag/loaders/text.d.ts +24 -0
- package/dist/types/rag/loaders/text.d.ts.map +1 -0
- package/dist/types/rag/splitDocuments.d.ts +22 -0
- package/dist/types/rag/splitDocuments.d.ts.map +1 -0
- package/dist/types/rag/splitters/byHeading.d.ts +37 -0
- package/dist/types/rag/splitters/byHeading.d.ts.map +1 -0
- package/dist/types/rag/splitters/byParagraph.d.ts +25 -0
- package/dist/types/rag/splitters/byParagraph.d.ts.map +1 -0
- package/dist/types/rag/splitters/constants.d.ts +20 -0
- package/dist/types/rag/splitters/constants.d.ts.map +1 -0
- package/dist/types/rag/splitters/fixedWithOverlap.d.ts +32 -0
- package/dist/types/rag/splitters/fixedWithOverlap.d.ts.map +1 -0
- package/dist/types/rag/splitters/index.d.ts +51 -0
- package/dist/types/rag/splitters/index.d.ts.map +1 -0
- package/dist/types/rag/splitters/shared.d.ts +88 -0
- package/dist/types/rag/splitters/shared.d.ts.map +1 -0
- package/dist/types/rag/splitters/wholeDocument.d.ts +17 -0
- package/dist/types/rag/splitters/wholeDocument.d.ts.map +1 -0
- package/dist/types/rag/types.d.ts +201 -0
- package/dist/types/rag/types.d.ts.map +1 -0
- package/package.json +23 -4
package/AGENTS.md
CHANGED
|
@@ -152,6 +152,8 @@ agent.rag(docs);
|
|
|
152
152
|
await agent.run({ message: 'How long do refunds take?' });
|
|
153
153
|
```
|
|
154
154
|
|
|
155
|
+
**Building the index** (8.10.0) is `agentfootprint/rag`: `indexFolder('./docs', { to: store, embedder })`, or the `loadDocuments` → `splitDocuments` → `indexCorpus` pieces, or `npx agentfootprint-index ./docs --to ./corpus.db`. Loaders for text/Markdown/HTML (zero-dep) and PDF (lazy `unpdf`, per-page text so citations can name a page). Re-running embeds only what changed. `defineRAG` stays on the MAIN barrel — it is run-time wiring; that door is index time.
|
|
156
|
+
|
|
155
157
|
`defineRAG` runs on `defineMemory({ type: SEMANTIC, strategy: TOP_K })`. Same machinery, three deliberate differences: a corpus is **read-only** (it never stores the conversation), it reads under its **own namespace** rather than the run's identity, and its chunks render as **citable `<source>` blocks**. For conversation memory alongside a corpus, register both — `.rag(defineRAG(...))` and `.memory(defineMemory(...))`, each with its own store.
|
|
156
158
|
|
|
157
159
|
**Why did the agent read this passage?** `agentfootprint.memory.retrieved` carries every candidate with its score — including the ones that were rejected and why. `agentfootprint.memory.attached` fires per chunk that reached the prompt. `agentfootprint.context.injected` reports `source: 'rag'` with that chunk's `retrievalScore` / `rankPosition` / `threshold`. The whole record is on root state as `retrievalEvidence_<id>`, where a backward slice can reach it.
|
package/CLAUDE.md
CHANGED
|
@@ -16,6 +16,7 @@ Entry points (package.json exports): `.` core API · `/observe` ALL observabilit
|
|
|
16
16
|
| adapters/ | hexagonal ports (types.ts = ALL port interfaces) + vendor impls (llm/, memory/, identity/, observability/). memory/sqliteVector.ts (8.9.0) = the only FULL MemoryStore we ship with `search` besides InMemoryStore — exact cosine over a resident Float32Array matrix, hydrated per namespace on first search and dropped on any write to it |
|
|
17
17
|
| recorders/core/ | bridges footprintjs events → typed EventDispatcher (ContextRecorder, EmitBridge, typedEmit) — auto-attached by Agent.createExecutor; most factories also exported via `/observe` for manual wiring (EmitBridge itself stays internal) |
|
|
18
18
|
| recorders/observability/ | consumer recorders over the typed stream (RunStepRecorder, FlowchartRecorder, Status, Trace replay) + `recordRun` — THE producer of a recording `{snapshot, events, structure}` (the shape lens's `observeRecording` consumes; `structure` = `getSpec().buildTimeStructure`, which no snapshot carries). Anything that saves a run goes through it |
|
|
19
|
+
| rag/ | (8.10.0, door `/rag`) index-TIME: `DocumentLoader` adapters (text/markdown/html zero-dep, pdf via lazy `unpdf`) + `Splitter` factories + `indexCorpus` — a REAL footprintjs chart whose commit log IS the indexing report. `defineRAG` deliberately stays on the MAIN barrel (run-time wiring); this door is the half that runs once, before any agent exists |
|
|
19
20
|
| lib/ | first-party sub-libraries: injection-engine/, context-bisect/ (localizeContextBug, toBacktrackTrace + sliceToBacktrackTrace — the atui board serializers), influence-core/, trace-toolpack/ (selfExplain; 6 tools incl. variable-first `backtrack(variable, element?)`), context-ledger/ (which pieces EARNED their tokens — post-run offers/uses/outcomes bookkeeping + demote-never-starve gates `ledgerToolGate`/`ledgerEntryScorer`/`ledgerGated`; grouped-mode folds sf-llm-call inner logs, unmeterable runs → undefined; /observe), mcp/, rag/, tool-lint/ |
|
|
20
21
|
| memory/ | store/ (MemoryStore port) + pipeline presets + stages + beats/facts + causal/ (dev-only, TOP_K+search()-only) + wire/mountMemoryPipeline + retrieval/ (8.8.0: the `RetrievalStrategy` seam + `RetrievalEvidence`, the record a retrieval leaves — `topK()` is what every earlier release did unnamed) |
|
|
21
22
|
| events/ | EventDispatcher (wildcard subs), registry (EVENT_NAMES, AgentfootprintEventMap), payloads |
|
|
@@ -39,6 +40,8 @@ Traps: `src/observability/` holds the finder IMPLEMENTATIONS (canonical home; `d
|
|
|
39
40
|
- **New typed event (3-step)**: payload interface in events/payloads.ts + entry in `AgentfootprintEventMap` (registry.ts:198) + append to `ALL_EVENT_TYPES` (registry.ts:488, count-asserted by tests). New DOMAIN also needs a bridge attach in Agent.createExecutor or emits never reach the dispatcher — AND a hand-edit to `DomainWildcard` (dispatcher.ts:67-82; already missing validation/credential/reliability).
|
|
40
41
|
- **Strategy (vendor sink)**: shapes in strategies/types.ts (Observability :130, Cost :169, LiveStatus :201, Lens :234); attach via `agent.enable.*` or `registerObservabilityStrategy` (strategies/registry.ts). New vendor = export from observability-providers.ts, NOT a new subpath.
|
|
41
42
|
- **Memory store**: implement `MemoryStore` (memory/store/types.ts:113; `search?` REQUIRED for causal memory); pass to `defineMemory({store})`. Memory TYPE/STRATEGY unions are CLOSED (define.types.ts:57/74 — new one edits defineMemory dispatch + a pipeline builder).
|
|
43
|
+
- **Document loader** (8.10.0): implement `DocumentLoader` (rag/types.ts) — `{name, extensions, load}`. `loadDocuments` routes by extension, caller-supplied loaders FIRST, so overriding a built-in is passing yours ahead of it rather than editing `DEFAULT_LOADERS`. A loader MUST NOT rewrite text after offsets are conceivable: the HTML stripper replaces tags with EQUAL-LENGTH whitespace for exactly this reason.
|
|
44
|
+
- **Splitter** (8.10.0): implement `Splitter` (`{name, split(doc) → SplitPiece[]}`), a factory function like the window/retrieval families. THE invariant — `doc.text.slice(charStart, charEnd) === piece.text` — is VERIFIED by `splitDocuments`, not trusted. All offset arithmetic lives once in `splitters/shared.ts`; a strategy that does its own is how the invariant breaks.
|
|
42
45
|
- **Durable store** (8.9.0): `sqliteVectorStore` follows `hosting/sqliteSessions` line for line — lazy `node:sqlite`, WAL read-back on `journalMode`, STRICT tables, schema-identity + schema-version refusals, `':memory:'` refused. TWO things it adds that have no precedent there: `putMany`/`putIfVersion`/`forget` wrap in a transaction (sqliteSessions has none), and the EMBEDDER FINGERPRINT (`'<id>@<dims>'`, one per namespace in `af_index_meta`) is refused at write AND query. `SqliteUnavailableError` is now ONE class in `lib/sqliteUnavailable.ts` re-exported by both doors — a second class of that name is a duplicate type the build refuses.
|
|
43
46
|
- **Retrieval rule** (8.8.0): implement `RetrievalStrategy` (memory/retrieval/types.ts) — `select(pool) → verdict[]`, one verdict per candidate, order preserved; it never touches the store and never embeds. Pass as `defineRAG({retrieval})`. `topK()` is the only shipped one; rerank/MMR are named-but-deferred adapters behind the same interface. `TopKStrategy` is a UNION whose arms exclude (`{topK,threshold}` vs `{retrieval}`) — refused in the type AND at runtime, because two spellings of one rule can disagree.
|
|
44
47
|
- **Injection/skill**: `Injection = {id, flavor, trigger, inject}` (lib/injection-engine/types.ts:161); trigger is a closed 4-variant union (:30 — new kind edits evaluator.ts:40-74 switch). Factories defineSkill etc.; skill graph via `skillGraph()` (skillGraph.ts:392) with pluggable `EntryScorer` (entryScorer.ts:64). `SkillGraphConfig` is a UNION (flat arm `start`/`steps` vs tree arm) — the contradictions it encodes are ALSO refused at build (`.tree()` + `.entry()`/`.route()`, a non-leaf in `skills[]` under a tree, two skills claiming one id, a second `.skillGraph()` on one agent).
|
|
@@ -51,6 +54,7 @@ Traps: `src/observability/` holds the finder IMPLEMENTATIONS (canonical home; `d
|
|
|
51
54
|
## Change-impact map
|
|
52
55
|
- **conventions.ts** (STAGE_IDS/SUBFLOW_IDS/INJECTION_KEYS) → chart builders that mount by id, ContextRecorder slot attribution, localizer loop-head detection (lib/context-bisect/trajectory.ts:17-33), `stageRole`/`milestoneFor` (Lens contract), BoundaryRecorder. Renaming an id is the whole blast radius.
|
|
53
56
|
- **BoundaryRecorder wiring is THREE connections, all at record time**: `runner.attach` (boundaries), `.subscribe(runner)` (what's inside them), `{getCommitCount}` (where each sits on the commit axis). The third fails SILENTLY — every event stamps `commitIdxBefore: 0`, `boundaryIndex` stays empty by design, and an offline step strip has nothing to place. Unrecoverable after the run (the commit log never records WHEN a boundary was crossed). Wired by `attachFlowchart` (which `enable.flowchart`/`enable.localObservability` both go through) and by `recordRun`; a new entry point must pass all three.
|
|
57
|
+
- **indexCorpus fan-out** (8.10.0) → `maxBranches` on `addParallelForEach` TRUNCATES surplus items rather than queueing them, so the chart fans out over a WINDOW (`take-window` → `embed` → `tally-window` → `more-batches-decider` `{loopTo: 'take-window'}`) that can never exceed the ceiling. A single fan-out over all batches would silently index only the first `maxConcurrentBatches` — pinned by the 12-batches-through-a-window-of-2 test. `embedded` is summed from each branch's `written`, never from the plan's queue, and the fan-out is `failFast: true` because a half-indexed corpus keeps answering.
|
|
54
58
|
- **Embedder fingerprint** (8.9.0) → `Embedder.id` (optional; every shipped embedder sets one, and NONE include dims — the store appends `@<dims>` itself, so an id carrying its own size double-stamps) + `indexDocuments` defaulting `embedderId` to it + `SqliteVectorStore.reconcileFingerprint` (the only comparison site). Rule: dimensions ALWAYS decide, model ids decide only when BOTH sides named themselves — refusing on an absent name would block the majority of callers who never pass `embedderId`.
|
|
55
59
|
- **Retrieval record** (8.8.0) → FOUR stages write one object in sequence: `loadRelevant` (candidates+scores+threshold verdicts) → `pickByBudget` (re-marks admitted→over-budget/over-max-entries) → `formatDefault` (`promptFragment` + `promptPosition`) → the read mount's outputMapper lifts it to root as `retrievalEvidence_<id>`. `memoryRecallInjections` then splits ONE recall into one ActiveInjection PER CHUNK — guarded by a byte-equality check (`fragments.join('\n\n') === systemContent`) that falls back to the single injection rather than change the prompt. `rank` (score order) and `promptPosition` (picker order) are DIFFERENT and both load-bearing: joining fragments in rank order reproduces the right bytes in a sequence the model never saw.
|
|
56
60
|
- **AgentState** → all 8 stages/ files, both builders' mappers, memory-wire STRING-TYPED keys ('runIdentity'/'turnNumber'/… buildAgentChart.ts:177-180 — not refactor-safe), finalizeResult's `reliabilityFail*`/`policyHalt*` reads (rename silently kills the typed errors).
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* agentfootprint-index — build a corpus index from the command line.
|
|
4
|
+
*
|
|
5
|
+
* The third bin in this package, after `agentfootprint-setup` and
|
|
6
|
+
* `agentfootprint-lint-tools`. It exists because indexing is a BOOT-TIME job:
|
|
7
|
+
* a cron entry, a deploy step, a thing you run once after adding documents.
|
|
8
|
+
* Making that a script every consumer writes for themselves — argument
|
|
9
|
+
* parsing, embedder selection, a progress line — is the kind of small friction
|
|
10
|
+
* that gets in the way of the first ten minutes.
|
|
11
|
+
*
|
|
12
|
+
* npx agentfootprint-index ./docs --to ./corpus.db
|
|
13
|
+
* npx agentfootprint-index ./docs --to ./corpus.db --embedder local --split heading
|
|
14
|
+
* npx agentfootprint-index ./docs --to ./corpus.db --dry-run
|
|
15
|
+
*
|
|
16
|
+
* It prints the same `IndexReport` the API returns, because the report IS the
|
|
17
|
+
* output: a second run over unchanged documents prints `embedded 0`, which is
|
|
18
|
+
* the whole point of an incremental index and the fastest way to see it work.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { parseArgs } from 'node:util';
|
|
22
|
+
import { existsSync, statSync } from 'node:fs';
|
|
23
|
+
import { resolve } from 'node:path';
|
|
24
|
+
|
|
25
|
+
const USAGE = `
|
|
26
|
+
agentfootprint-index — build a corpus index
|
|
27
|
+
|
|
28
|
+
USAGE
|
|
29
|
+
agentfootprint-index <dir> --to <file.db> [options]
|
|
30
|
+
|
|
31
|
+
REQUIRED
|
|
32
|
+
<dir> Directory of documents to index
|
|
33
|
+
--to <file.db> SQLite index file (created if missing)
|
|
34
|
+
|
|
35
|
+
OPTIONS
|
|
36
|
+
--embedder <name> static (default) | local | openai | mock
|
|
37
|
+
static: bundled weights, no key, no network
|
|
38
|
+
local: on-device sentence-transformer, no key
|
|
39
|
+
openai: hosted, needs OPENAI_API_KEY
|
|
40
|
+
mock: letter frequency — plumbing only, NOT semantic
|
|
41
|
+
--split <name> heading (default) | paragraph | fixed | whole
|
|
42
|
+
--chars <n> Target chunk size. Default 1000
|
|
43
|
+
--overlap <n> Overlap between chunks. Default 150
|
|
44
|
+
--include <.ext,...> Extensions to index. Default: every format we read
|
|
45
|
+
--corpus <name> Namespace to index into. Default '_global'
|
|
46
|
+
--no-recursive Do not descend into subdirectories
|
|
47
|
+
--no-remove Keep chunks whose document disappeared
|
|
48
|
+
--dry-run Report what WOULD happen; write nothing
|
|
49
|
+
--json Print the report as JSON
|
|
50
|
+
-h, --help This
|
|
51
|
+
|
|
52
|
+
EXAMPLES
|
|
53
|
+
agentfootprint-index ./docs --to ./corpus.db
|
|
54
|
+
agentfootprint-index ./docs --to ./corpus.db --embedder local --chars 800
|
|
55
|
+
agentfootprint-index ./docs --to ./corpus.db --dry-run --json
|
|
56
|
+
`;
|
|
57
|
+
|
|
58
|
+
function fail(message) {
|
|
59
|
+
process.stderr.write(`agentfootprint-index: ${message}\n`);
|
|
60
|
+
process.stderr.write('Run with --help for usage.\n');
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const { values, positionals } = (() => {
|
|
65
|
+
try {
|
|
66
|
+
return parseArgs({
|
|
67
|
+
allowPositionals: true,
|
|
68
|
+
options: {
|
|
69
|
+
to: { type: 'string' },
|
|
70
|
+
embedder: { type: 'string' },
|
|
71
|
+
split: { type: 'string' },
|
|
72
|
+
chars: { type: 'string' },
|
|
73
|
+
overlap: { type: 'string' },
|
|
74
|
+
include: { type: 'string' },
|
|
75
|
+
corpus: { type: 'string' },
|
|
76
|
+
recursive: { type: 'boolean', default: true },
|
|
77
|
+
remove: { type: 'boolean', default: true },
|
|
78
|
+
'dry-run': { type: 'boolean', default: false },
|
|
79
|
+
json: { type: 'boolean', default: false },
|
|
80
|
+
help: { type: 'boolean', short: 'h', default: false },
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
} catch (err) {
|
|
84
|
+
fail(err instanceof Error ? err.message : String(err));
|
|
85
|
+
}
|
|
86
|
+
})();
|
|
87
|
+
|
|
88
|
+
if (values.help || positionals.length === 0) {
|
|
89
|
+
process.stdout.write(USAGE);
|
|
90
|
+
process.exit(values.help ? 0 : 1);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const dir = resolve(positionals[0]);
|
|
94
|
+
if (!existsSync(dir) || !statSync(dir).isDirectory()) {
|
|
95
|
+
fail(`'${positionals[0]}' is not a directory.`);
|
|
96
|
+
}
|
|
97
|
+
if (!values.to && !values['dry-run']) {
|
|
98
|
+
fail('--to <file.db> is required (or use --dry-run to report without writing).');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Imported through the package's own entry points, so the CLI exercises the
|
|
102
|
+
// same doors a consumer does — a break in the export map fails here too.
|
|
103
|
+
const { indexCorpus } = await import('../dist/esm/doors/rag.js');
|
|
104
|
+
const { byHeading, byParagraph, fixedWithOverlap, wholeDocument } = await import(
|
|
105
|
+
'../dist/esm/doors/rag.js'
|
|
106
|
+
);
|
|
107
|
+
const { sqliteVectorStore, InMemoryStore, mockEmbedder } = await import(
|
|
108
|
+
'../dist/esm/doors/memory.js'
|
|
109
|
+
);
|
|
110
|
+
const providers = await import('../dist/esm/doors/providers.js');
|
|
111
|
+
|
|
112
|
+
const chars = values.chars === undefined ? undefined : Number(values.chars);
|
|
113
|
+
const overlap = values.overlap === undefined ? undefined : Number(values.overlap);
|
|
114
|
+
if (chars !== undefined && !Number.isFinite(chars)) fail('--chars must be a number.');
|
|
115
|
+
if (overlap !== undefined && !Number.isFinite(overlap)) fail('--overlap must be a number.');
|
|
116
|
+
|
|
117
|
+
const splitters = {
|
|
118
|
+
heading: () => byHeading({ ...(chars && { maxChars: chars }), ...(overlap !== undefined && { overlapChars: overlap }) }),
|
|
119
|
+
paragraph: () => byParagraph({ ...(chars && { maxChars: chars }), ...(overlap !== undefined && { overlapChars: overlap }) }),
|
|
120
|
+
fixed: () => fixedWithOverlap({ ...(chars && { chars }), ...(overlap !== undefined && { overlapChars: overlap }) }),
|
|
121
|
+
whole: () => wholeDocument(),
|
|
122
|
+
};
|
|
123
|
+
const splitName = values.split ?? 'heading';
|
|
124
|
+
if (!(splitName in splitters)) {
|
|
125
|
+
fail(`unknown --split '${splitName}'. One of: ${Object.keys(splitters).join(', ')}.`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const embedders = {
|
|
129
|
+
static: () => providers.staticEmbedder(),
|
|
130
|
+
local: () => providers.localEmbedder(),
|
|
131
|
+
openai: () => providers.openaiEmbedder(),
|
|
132
|
+
mock: () => mockEmbedder(),
|
|
133
|
+
};
|
|
134
|
+
const embedderName = values.embedder ?? 'static';
|
|
135
|
+
if (!(embedderName in embedders)) {
|
|
136
|
+
fail(`unknown --embedder '${embedderName}'. One of: ${Object.keys(embedders).join(', ')}.`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let embedder;
|
|
140
|
+
try {
|
|
141
|
+
embedder = embedders[embedderName]();
|
|
142
|
+
} catch (err) {
|
|
143
|
+
fail(err instanceof Error ? err.message : String(err));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// --dry-run writes to a throwaway in-memory index, so it reports real chunk
|
|
147
|
+
// counts and real splitting without touching the file. It still EMBEDS —
|
|
148
|
+
// the alternative is reporting a count that the real run might not match.
|
|
149
|
+
const store = values['dry-run'] ? new InMemoryStore() : sqliteVectorStore({ file: resolve(values.to) });
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
const report = await indexCorpus({
|
|
153
|
+
source: {
|
|
154
|
+
dir,
|
|
155
|
+
recursive: values.recursive,
|
|
156
|
+
...(values.include && { include: values.include.split(',').map((e) => e.trim()) }),
|
|
157
|
+
},
|
|
158
|
+
store,
|
|
159
|
+
embedder,
|
|
160
|
+
splitter: splitters[splitName](),
|
|
161
|
+
removeMissing: values.remove,
|
|
162
|
+
...(values.corpus && { corpus: { conversationId: values.corpus } }),
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
if (values.json) {
|
|
166
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
167
|
+
} else {
|
|
168
|
+
const parts = [
|
|
169
|
+
`discovered ${report.discovered}`,
|
|
170
|
+
`loaded ${report.loaded}`,
|
|
171
|
+
`chunks ${report.chunks}`,
|
|
172
|
+
`embedded ${report.embedded}`,
|
|
173
|
+
`skipped ${report.skipped}`,
|
|
174
|
+
`removed ${report.removed}`,
|
|
175
|
+
];
|
|
176
|
+
process.stdout.write(
|
|
177
|
+
`${values['dry-run'] ? '[dry run] ' : ''}${parts.join(' · ')} (${report.elapsedMs}ms, ${report.splitter}, ${report.embedderFingerprint})\n`,
|
|
178
|
+
);
|
|
179
|
+
for (const failure of report.failed) {
|
|
180
|
+
process.stdout.write(` failed: ${failure.uri} — ${failure.reason}\n`);
|
|
181
|
+
}
|
|
182
|
+
if (report.truncated.length > 0) {
|
|
183
|
+
process.stdout.write(
|
|
184
|
+
` ${report.truncated.length} chunk(s) longer than the embedder reads — they were clipped. Lower --chars.\n`,
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (typeof store.close === 'function') store.close();
|
|
189
|
+
} catch (err) {
|
|
190
|
+
process.stderr.write(`agentfootprint-index: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
191
|
+
process.exit(1);
|
|
192
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* agentfootprint/rag — turning a folder of documents into something an agent
|
|
4
|
+
* can retrieve from.
|
|
5
|
+
*
|
|
6
|
+
* Loaders (text, Markdown, HTML, PDF), splitters, and the indexing chart that
|
|
7
|
+
* runs them: everything between "I have some documents" and "the vectors are
|
|
8
|
+
* in a store".
|
|
9
|
+
*
|
|
10
|
+
* ── Why this is its own door ────────────────────────────────────────────────
|
|
11
|
+
* Index time is a different PROCESS from run time. It happens at boot, on a
|
|
12
|
+
* cron, or from a CLI; it touches the filesystem; and for PDFs it reaches for
|
|
13
|
+
* an optional peer dependency. None of that belongs in the bundle of an agent
|
|
14
|
+
* that only wants to answer a question — a browser or edge runtime importing
|
|
15
|
+
* `agentfootprint` should never resolve `node:fs`. A door is the unit at which
|
|
16
|
+
* a bundler can cut, and this is the cut.
|
|
17
|
+
*
|
|
18
|
+
* ── `defineRAG` is NOT here, deliberately ───────────────────────────────────
|
|
19
|
+
* The retriever stays on the MAIN barrel, with `indexDocuments`, because it is
|
|
20
|
+
* run-time wiring: it is registered on an agent, it runs on every turn, and it
|
|
21
|
+
* belongs beside `defineTool` and the rest of the wiring surface. This door is
|
|
22
|
+
* the other half of the story — the half that runs once, before any agent
|
|
23
|
+
* exists.
|
|
24
|
+
*
|
|
25
|
+
* import { defineRAG, indexDocuments } from 'agentfootprint'; // wiring
|
|
26
|
+
* import { sqliteVectorStore } from 'agentfootprint/memory'; // where it lives
|
|
27
|
+
* import { indexFolder, byHeading } from 'agentfootprint/rag'; // building it
|
|
28
|
+
*
|
|
29
|
+
* ── Deferred, with destinations named ───────────────────────────────────────
|
|
30
|
+
* **Re-ranking and MMR** are retrieval-side concerns and belong behind
|
|
31
|
+
* `RetrievalStrategy` (`agentfootprint/memory`), not here — a re-ranker is a
|
|
32
|
+
* different rule for choosing among candidates, not a different way of
|
|
33
|
+
* building an index. **File watching** is deliberately absent: `indexCorpus`
|
|
34
|
+
* is an explicit call because magic that re-indexes behind you is magic that
|
|
35
|
+
* re-bills you. Run it at boot, from a cron, or from the CLI.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```ts
|
|
39
|
+
* import { indexFolder } from 'agentfootprint/rag';
|
|
40
|
+
* import { sqliteVectorStore } from 'agentfootprint/memory';
|
|
41
|
+
* import { staticEmbedder } from 'agentfootprint/providers';
|
|
42
|
+
*
|
|
43
|
+
* const report = await indexFolder('./docs', {
|
|
44
|
+
* to: sqliteVectorStore({ file: './corpus.db' }),
|
|
45
|
+
* embedder: staticEmbedder(),
|
|
46
|
+
* });
|
|
47
|
+
* // { discovered: 3, loaded: 3, chunks: 14, embedded: 14, skipped: 0, removed: 0, … }
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
51
|
+
if (k2 === undefined) k2 = k;
|
|
52
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
53
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
54
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
55
|
+
}
|
|
56
|
+
Object.defineProperty(o, k2, desc);
|
|
57
|
+
}) : (function(o, m, k, k2) {
|
|
58
|
+
if (k2 === undefined) k2 = k;
|
|
59
|
+
o[k2] = m[k];
|
|
60
|
+
}));
|
|
61
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
62
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
63
|
+
};
|
|
64
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
65
|
+
__exportStar(require("../rag/index.js"), exports);
|
|
66
|
+
//# sourceMappingURL=rag.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rag.js","sourceRoot":"","sources":["../../src/doors/rag.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;;;;;;;;;;;;;;;;AAEH,kDAAgC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agentfootprint/rag — turning a folder of documents into something an agent
|
|
3
|
+
* can retrieve from.
|
|
4
|
+
*
|
|
5
|
+
* Loaders (text, Markdown, HTML, PDF), splitters, and the indexing chart that
|
|
6
|
+
* runs them: everything between "I have some documents" and "the vectors are
|
|
7
|
+
* in a store".
|
|
8
|
+
*
|
|
9
|
+
* ── Why this is its own door ────────────────────────────────────────────────
|
|
10
|
+
* Index time is a different PROCESS from run time. It happens at boot, on a
|
|
11
|
+
* cron, or from a CLI; it touches the filesystem; and for PDFs it reaches for
|
|
12
|
+
* an optional peer dependency. None of that belongs in the bundle of an agent
|
|
13
|
+
* that only wants to answer a question — a browser or edge runtime importing
|
|
14
|
+
* `agentfootprint` should never resolve `node:fs`. A door is the unit at which
|
|
15
|
+
* a bundler can cut, and this is the cut.
|
|
16
|
+
*
|
|
17
|
+
* ── `defineRAG` is NOT here, deliberately ───────────────────────────────────
|
|
18
|
+
* The retriever stays on the MAIN barrel, with `indexDocuments`, because it is
|
|
19
|
+
* run-time wiring: it is registered on an agent, it runs on every turn, and it
|
|
20
|
+
* belongs beside `defineTool` and the rest of the wiring surface. This door is
|
|
21
|
+
* the other half of the story — the half that runs once, before any agent
|
|
22
|
+
* exists.
|
|
23
|
+
*
|
|
24
|
+
* import { defineRAG, indexDocuments } from 'agentfootprint'; // wiring
|
|
25
|
+
* import { sqliteVectorStore } from 'agentfootprint/memory'; // where it lives
|
|
26
|
+
* import { indexFolder, byHeading } from 'agentfootprint/rag'; // building it
|
|
27
|
+
*
|
|
28
|
+
* ── Deferred, with destinations named ───────────────────────────────────────
|
|
29
|
+
* **Re-ranking and MMR** are retrieval-side concerns and belong behind
|
|
30
|
+
* `RetrievalStrategy` (`agentfootprint/memory`), not here — a re-ranker is a
|
|
31
|
+
* different rule for choosing among candidates, not a different way of
|
|
32
|
+
* building an index. **File watching** is deliberately absent: `indexCorpus`
|
|
33
|
+
* is an explicit call because magic that re-indexes behind you is magic that
|
|
34
|
+
* re-bills you. Run it at boot, from a cron, or from the CLI.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* import { indexFolder } from 'agentfootprint/rag';
|
|
39
|
+
* import { sqliteVectorStore } from 'agentfootprint/memory';
|
|
40
|
+
* import { staticEmbedder } from 'agentfootprint/providers';
|
|
41
|
+
*
|
|
42
|
+
* const report = await indexFolder('./docs', {
|
|
43
|
+
* to: sqliteVectorStore({ file: './corpus.db' }),
|
|
44
|
+
* embedder: staticEmbedder(),
|
|
45
|
+
* });
|
|
46
|
+
* // { discovered: 3, loaded: 3, chunks: 14, embedded: 14, skipped: 0, removed: 0, … }
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export * from '../rag/index.js';
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agentfootprint/rag — turning a folder of documents into something an agent
|
|
3
|
+
* can retrieve from.
|
|
4
|
+
*
|
|
5
|
+
* Loaders (text, Markdown, HTML, PDF), splitters, and the indexing chart that
|
|
6
|
+
* runs them: everything between "I have some documents" and "the vectors are
|
|
7
|
+
* in a store".
|
|
8
|
+
*
|
|
9
|
+
* ── Why this is its own door ────────────────────────────────────────────────
|
|
10
|
+
* Index time is a different PROCESS from run time. It happens at boot, on a
|
|
11
|
+
* cron, or from a CLI; it touches the filesystem; and for PDFs it reaches for
|
|
12
|
+
* an optional peer dependency. None of that belongs in the bundle of an agent
|
|
13
|
+
* that only wants to answer a question — a browser or edge runtime importing
|
|
14
|
+
* `agentfootprint` should never resolve `node:fs`. A door is the unit at which
|
|
15
|
+
* a bundler can cut, and this is the cut.
|
|
16
|
+
*
|
|
17
|
+
* ── `defineRAG` is NOT here, deliberately ───────────────────────────────────
|
|
18
|
+
* The retriever stays on the MAIN barrel, with `indexDocuments`, because it is
|
|
19
|
+
* run-time wiring: it is registered on an agent, it runs on every turn, and it
|
|
20
|
+
* belongs beside `defineTool` and the rest of the wiring surface. This door is
|
|
21
|
+
* the other half of the story — the half that runs once, before any agent
|
|
22
|
+
* exists.
|
|
23
|
+
*
|
|
24
|
+
* import { defineRAG, indexDocuments } from 'agentfootprint'; // wiring
|
|
25
|
+
* import { sqliteVectorStore } from 'agentfootprint/memory'; // where it lives
|
|
26
|
+
* import { indexFolder, byHeading } from 'agentfootprint/rag'; // building it
|
|
27
|
+
*
|
|
28
|
+
* ── Deferred, with destinations named ───────────────────────────────────────
|
|
29
|
+
* **Re-ranking and MMR** are retrieval-side concerns and belong behind
|
|
30
|
+
* `RetrievalStrategy` (`agentfootprint/memory`), not here — a re-ranker is a
|
|
31
|
+
* different rule for choosing among candidates, not a different way of
|
|
32
|
+
* building an index. **File watching** is deliberately absent: `indexCorpus`
|
|
33
|
+
* is an explicit call because magic that re-indexes behind you is magic that
|
|
34
|
+
* re-bills you. Run it at boot, from a cron, or from the CLI.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* import { indexFolder } from 'agentfootprint/rag';
|
|
39
|
+
* import { sqliteVectorStore } from 'agentfootprint/memory';
|
|
40
|
+
* import { staticEmbedder } from 'agentfootprint/providers';
|
|
41
|
+
*
|
|
42
|
+
* const report = await indexFolder('./docs', {
|
|
43
|
+
* to: sqliteVectorStore({ file: './corpus.db' }),
|
|
44
|
+
* embedder: staticEmbedder(),
|
|
45
|
+
* });
|
|
46
|
+
* // { discovered: 3, loaded: 3, chunks: 14, embedded: 14, skipped: 0, removed: 0, … }
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export * from '../rag/index.js';
|
|
50
|
+
//# sourceMappingURL=rag.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rag.js","sourceRoot":"","sources":["../../../src/doors/rag.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAEH,cAAc,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sha256 — the content hash incremental re-indexing skips on.
|
|
3
|
+
*
|
|
4
|
+
* Pattern: leaf utility over `node:crypto`.
|
|
5
|
+
* Role: rag/ layer. Distinct from `lib/fnv1a` on purpose: FNV-1a identifies
|
|
6
|
+
* content for correlation inside one recording, where a collision
|
|
7
|
+
* costs a confusing trace. This one decides whether a document is
|
|
8
|
+
* RE-EMBEDDED, where a collision costs a stale answer that looks
|
|
9
|
+
* exactly like a fresh one — and it is compared across runs, machines
|
|
10
|
+
* and months rather than within a single process.
|
|
11
|
+
* Emits: N/A.
|
|
12
|
+
*/
|
|
13
|
+
import { lazyRequire } from '../lib/lazyRequire.js';
|
|
14
|
+
/** Hex sha-256 of the bytes or text given. */
|
|
15
|
+
export function sha256(input) {
|
|
16
|
+
const crypto = lazyRequire('node:crypto');
|
|
17
|
+
return crypto
|
|
18
|
+
.createHash('sha256')
|
|
19
|
+
.update(typeof input === 'string' ? Buffer.from(input, 'utf8') : Buffer.from(input))
|
|
20
|
+
.digest('hex');
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=hash.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hash.js","sourceRoot":"","sources":["../../../src/rag/hash.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEpD,8CAA8C;AAC9C,MAAM,UAAU,MAAM,CAAC,KAA0B;IAC/C,MAAM,MAAM,GAAG,WAAW,CAA+B,aAAa,CAAC,CAAC;IACxE,OAAO,MAAM;SACV,UAAU,CAAC,QAAQ,CAAC;SACpB,MAAM,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;SACnF,MAAM,CAAC,KAAK,CAAC,CAAC;AACnB,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rag/ — a folder of documents becomes an answering agent.
|
|
3
|
+
*
|
|
4
|
+
* @see ./types.ts the ports and the value objects
|
|
5
|
+
* @see ./loadDocuments.ts step 1 — bytes to text
|
|
6
|
+
* @see ./splitDocuments.ts step 2 — text to chunks that know where they came from
|
|
7
|
+
* @see ./indexCorpus.ts step 3 — the chart that embeds and stores them
|
|
8
|
+
*/
|
|
9
|
+
export { loadDocuments, type LoadDocumentsOptions, type LoadDocumentsResult, } from './loadDocuments.js';
|
|
10
|
+
export { splitDocuments, type SplitDocumentsOptions } from './splitDocuments.js';
|
|
11
|
+
export { indexCorpus, buildIndexChart, type IndexCorpusConfig } from './indexCorpus.js';
|
|
12
|
+
export { indexFolder, type IndexFolderOptions } from './indexFolder.js';
|
|
13
|
+
export { sha256 } from './hash.js';
|
|
14
|
+
export { DEFAULT_LOADERS, textLoader, markdownLoader, htmlLoader, pdfLoader, mockLoader, MissingPdfSupportError, type PdfLoaderOptions, type MockLoaderOptions, } from './loaders/index.js';
|
|
15
|
+
export { stripTags } from './loaders/html.js';
|
|
16
|
+
export { byParagraph, byHeading, fixedWithOverlap, wholeDocument, DEFAULT_MAX_CHARS, DEFAULT_OVERLAP_CHARS, type ByParagraphOptions, type ByHeadingOptions, type FixedWithOverlapOptions, } from './splitters/index.js';
|
|
17
|
+
export type { Chunk, DocumentInput, DocumentLoader, DocumentSource, FailedDocument, IndexReport, LoadedDocument, LoadedDocumentDraft, Splitter, SplitPiece, TruncatedChunk, } from './types.js';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rag/ — a folder of documents becomes an answering agent.
|
|
3
|
+
*
|
|
4
|
+
* @see ./types.ts the ports and the value objects
|
|
5
|
+
* @see ./loadDocuments.ts step 1 — bytes to text
|
|
6
|
+
* @see ./splitDocuments.ts step 2 — text to chunks that know where they came from
|
|
7
|
+
* @see ./indexCorpus.ts step 3 — the chart that embeds and stores them
|
|
8
|
+
*/
|
|
9
|
+
export { loadDocuments, } from './loadDocuments.js';
|
|
10
|
+
export { splitDocuments } from './splitDocuments.js';
|
|
11
|
+
export { indexCorpus, buildIndexChart } from './indexCorpus.js';
|
|
12
|
+
export { indexFolder } from './indexFolder.js';
|
|
13
|
+
export { sha256 } from './hash.js';
|
|
14
|
+
export { DEFAULT_LOADERS, textLoader, markdownLoader, htmlLoader, pdfLoader, mockLoader, MissingPdfSupportError, } from './loaders/index.js';
|
|
15
|
+
export { stripTags } from './loaders/html.js';
|
|
16
|
+
export { byParagraph, byHeading, fixedWithOverlap, wholeDocument, DEFAULT_MAX_CHARS, DEFAULT_OVERLAP_CHARS, } from './splitters/index.js';
|
|
17
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/rag/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EACL,aAAa,GAGd,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,cAAc,EAA8B,MAAM,qBAAqB,CAAC;AACjF,OAAO,EAAE,WAAW,EAAE,eAAe,EAA0B,MAAM,kBAAkB,CAAC;AACxF,OAAO,EAAE,WAAW,EAA2B,MAAM,kBAAkB,CAAC;AACxE,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAEnC,OAAO,EACL,eAAe,EACf,UAAU,EACV,cAAc,EACd,UAAU,EACV,SAAS,EACT,UAAU,EACV,sBAAsB,GAGvB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAE9C,OAAO,EACL,WAAW,EACX,SAAS,EACT,gBAAgB,EAChB,aAAa,EACb,iBAAiB,EACjB,qBAAqB,GAItB,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { TypedScope } from 'footprintjs';
|
|
2
|
+
import type { Embedder } from '../memory/embedding/index.js';
|
|
3
|
+
import type { MemoryStore } from '../memory/store/index.js';
|
|
4
|
+
import type { MemoryIdentity } from '../memory/identity/index.js';
|
|
5
|
+
import type { Chunk, DocumentLoader, DocumentSource, FailedDocument, IndexReport, LoadedDocument, Splitter, TruncatedChunk } from './types.js';
|
|
6
|
+
export interface IndexCorpusConfig {
|
|
7
|
+
/** Where the documents come from. */
|
|
8
|
+
readonly source: DocumentSource;
|
|
9
|
+
/** Where the vectors go. Any `MemoryStore`; `sqliteVectorStore` to keep them. */
|
|
10
|
+
readonly store: MemoryStore;
|
|
11
|
+
/** What turns chunk text into vectors. */
|
|
12
|
+
readonly embedder: Embedder;
|
|
13
|
+
/**
|
|
14
|
+
* The namespace to index into. Defaults to the same
|
|
15
|
+
* `{ conversationId: '_global' }` that `defineRAG` reads from, so index with
|
|
16
|
+
* no options and retrieve with no options and the two meet.
|
|
17
|
+
*/
|
|
18
|
+
readonly corpus?: MemoryIdentity;
|
|
19
|
+
/**
|
|
20
|
+
* How to cut the documents. Default `byHeading()` — the one strategy that
|
|
21
|
+
* reads the document's own structure instead of inferring it.
|
|
22
|
+
*/
|
|
23
|
+
readonly splitter?: Splitter;
|
|
24
|
+
/** Loaders consulted before the built-ins. */
|
|
25
|
+
readonly loaders?: readonly DocumentLoader[];
|
|
26
|
+
/**
|
|
27
|
+
* The embedder's id, stored per vector and refused on when it changes.
|
|
28
|
+
* Defaults to `embedder.id`.
|
|
29
|
+
*/
|
|
30
|
+
readonly embedderId?: string;
|
|
31
|
+
/** Chunks per embed batch. Default 64. */
|
|
32
|
+
readonly batchSize?: number;
|
|
33
|
+
/**
|
|
34
|
+
* How many batches embed at once. Default 4.
|
|
35
|
+
*
|
|
36
|
+
* A hard ceiling on PARALLELISM, not on total work: batches beyond it wait
|
|
37
|
+
* for the next window rather than being dropped.
|
|
38
|
+
*/
|
|
39
|
+
readonly maxConcurrentBatches?: number;
|
|
40
|
+
/** Attempts per batch, including the first. Default 3. */
|
|
41
|
+
readonly attempts?: number;
|
|
42
|
+
/**
|
|
43
|
+
* Delete chunks whose document is no longer in the source. Default true —
|
|
44
|
+
* an index that answers from a document you deleted is worse than one that
|
|
45
|
+
* does not answer.
|
|
46
|
+
*/
|
|
47
|
+
readonly removeMissing?: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* The embedder's input ceiling in characters. Chunks longer than this are
|
|
50
|
+
* embedded anyway (the embedder clips them) and RECORDED in
|
|
51
|
+
* `report.truncated` — so silent half-embedding becomes a number you can see.
|
|
52
|
+
* Default 2000, the measured `localEmbedder` cliff.
|
|
53
|
+
*/
|
|
54
|
+
readonly maxChunkChars?: number;
|
|
55
|
+
}
|
|
56
|
+
/** The chart's scope. Every field here is in the commit log. */
|
|
57
|
+
interface IndexState {
|
|
58
|
+
discoveredCount: number;
|
|
59
|
+
documents: readonly LoadedDocument[];
|
|
60
|
+
failed: readonly FailedDocument[];
|
|
61
|
+
chunks: readonly Chunk[];
|
|
62
|
+
toEmbed: readonly Chunk[];
|
|
63
|
+
toSkip: readonly string[];
|
|
64
|
+
toRemove: readonly string[];
|
|
65
|
+
/** The queue of batches still to embed. Drained one window at a time. */
|
|
66
|
+
pendingBatches: readonly (readonly Chunk[])[];
|
|
67
|
+
/** The window being fanned out over — never longer than `maxConcurrentBatches`. */
|
|
68
|
+
window: readonly (readonly Chunk[])[];
|
|
69
|
+
batchResults: readonly ({
|
|
70
|
+
written?: number;
|
|
71
|
+
} | undefined)[];
|
|
72
|
+
embeddedCount: number;
|
|
73
|
+
removedCount: number;
|
|
74
|
+
truncated: readonly TruncatedChunk[];
|
|
75
|
+
report?: IndexReport;
|
|
76
|
+
[key: string]: unknown;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Build and run the indexing chart.
|
|
80
|
+
*
|
|
81
|
+
* @returns the report — which is also committed to the run's own log, so it is
|
|
82
|
+
* evidence rather than only a return value.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```ts
|
|
86
|
+
* const report = await indexCorpus({
|
|
87
|
+
* source: { dir: './docs' },
|
|
88
|
+
* store: sqliteVectorStore({ file: './corpus.db' }),
|
|
89
|
+
* embedder: staticEmbedder(),
|
|
90
|
+
* });
|
|
91
|
+
* // { discovered: 3, loaded: 3, chunks: 14, embedded: 14, skipped: 0, removed: 0, … }
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
export declare function indexCorpus(config: IndexCorpusConfig): Promise<IndexReport>;
|
|
95
|
+
/**
|
|
96
|
+
* The chart itself, exposed so a caller can mount it, attach recorders to it,
|
|
97
|
+
* or run it under their own executor — the same freedom every other chart in
|
|
98
|
+
* this library gives.
|
|
99
|
+
*/
|
|
100
|
+
export declare function buildIndexChart(config: IndexCorpusConfig): ReturnType<typeof compile>;
|
|
101
|
+
declare function compile(config: IndexCorpusConfig): import("footprintjs").RunnableFlowChart<any, TypedScope<IndexState>>;
|
|
102
|
+
export {};
|