agentfootprint 8.7.0 → 8.8.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 +12 -4
- package/CLAUDE.md +4 -2
- package/ai-instructions/claude-code/SKILL.md +1 -1
- package/ai-instructions/setup.sh +0 -0
- package/bin/agentfootprint-lint-tools.mjs +0 -0
- package/dist/core/agent/buildAgentChart.js +12 -1
- package/dist/core/agent/buildAgentChart.js.map +1 -1
- package/dist/core/agent/buildDynamicAgentChart.js +12 -1
- package/dist/core/agent/buildDynamicAgentChart.js.map +1 -1
- package/dist/core/agent/memoryRecallInjections.js +100 -10
- package/dist/core/agent/memoryRecallInjections.js.map +1 -1
- package/dist/core/agent/stages/deliver.js.map +1 -1
- package/dist/core/slots/buildSystemPromptSlot.js +10 -0
- package/dist/core/slots/buildSystemPromptSlot.js.map +1 -1
- package/dist/core/slots/helpers.js +12 -10
- package/dist/core/slots/helpers.js.map +1 -1
- package/dist/esm/core/agent/buildAgentChart.js +13 -2
- package/dist/esm/core/agent/buildAgentChart.js.map +1 -1
- package/dist/esm/core/agent/buildDynamicAgentChart.js +13 -2
- package/dist/esm/core/agent/buildDynamicAgentChart.js.map +1 -1
- package/dist/esm/core/agent/memoryRecallInjections.d.ts +13 -3
- package/dist/esm/core/agent/memoryRecallInjections.js +101 -11
- package/dist/esm/core/agent/memoryRecallInjections.js.map +1 -1
- package/dist/esm/core/agent/stages/deliver.d.ts +6 -3
- package/dist/esm/core/agent/stages/deliver.js.map +1 -1
- package/dist/esm/core/slots/buildSystemPromptSlot.js +10 -0
- package/dist/esm/core/slots/buildSystemPromptSlot.js.map +1 -1
- package/dist/esm/core/slots/helpers.d.ts +11 -2
- package/dist/esm/core/slots/helpers.js +11 -9
- package/dist/esm/core/slots/helpers.js.map +1 -1
- package/dist/esm/events/payloads.d.ts +63 -0
- package/dist/esm/events/registry.d.ts +3 -1
- package/dist/esm/events/registry.js +2 -0
- package/dist/esm/events/registry.js.map +1 -1
- package/dist/esm/index.d.ts +2 -1
- package/dist/esm/index.js +6 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/lib/fnv1a.d.ts +16 -0
- package/dist/esm/lib/fnv1a.js +24 -0
- package/dist/esm/lib/fnv1a.js.map +1 -0
- package/dist/esm/lib/injection-engine/types.d.ts +17 -0
- package/dist/esm/lib/injection-engine/types.js.map +1 -1
- package/dist/esm/lib/rag/defineRAG.d.ts +126 -26
- package/dist/esm/lib/rag/defineRAG.js +112 -25
- package/dist/esm/lib/rag/defineRAG.js.map +1 -1
- package/dist/esm/lib/rag/index.d.ts +1 -1
- package/dist/esm/lib/rag/index.js +1 -1
- package/dist/esm/lib/rag/index.js.map +1 -1
- package/dist/esm/memory/define.js +38 -2
- package/dist/esm/memory/define.js.map +1 -1
- package/dist/esm/memory/define.types.d.ts +93 -1
- package/dist/esm/memory/define.types.js +18 -0
- package/dist/esm/memory/define.types.js.map +1 -1
- package/dist/esm/memory/embedding/loadRelevant.d.ts +49 -15
- package/dist/esm/memory/embedding/loadRelevant.js +136 -7
- package/dist/esm/memory/embedding/loadRelevant.js.map +1 -1
- package/dist/esm/memory/index.d.ts +2 -1
- package/dist/esm/memory/index.js +2 -1
- package/dist/esm/memory/index.js.map +1 -1
- package/dist/esm/memory/pipeline/semantic.d.ts +15 -0
- package/dist/esm/memory/pipeline/semantic.js +3 -1
- package/dist/esm/memory/pipeline/semantic.js.map +1 -1
- package/dist/esm/memory/retrieval/index.d.ts +10 -0
- package/dist/esm/memory/retrieval/index.js +3 -0
- package/dist/esm/memory/retrieval/index.js.map +1 -0
- package/dist/esm/memory/retrieval/provenance.d.ts +43 -0
- package/dist/esm/memory/retrieval/provenance.js +60 -0
- package/dist/esm/memory/retrieval/provenance.js.map +1 -0
- package/dist/esm/memory/retrieval/topK.d.ts +67 -0
- package/dist/esm/memory/retrieval/topK.js +53 -0
- package/dist/esm/memory/retrieval/topK.js.map +1 -0
- package/dist/esm/memory/retrieval/types.d.ts +189 -0
- package/dist/esm/memory/retrieval/types.js +2 -0
- package/dist/esm/memory/retrieval/types.js.map +1 -0
- package/dist/esm/memory/stages/formatDefault.d.ts +52 -26
- package/dist/esm/memory/stages/formatDefault.js +118 -28
- package/dist/esm/memory/stages/formatDefault.js.map +1 -1
- package/dist/esm/memory/stages/pickByBudget.js +53 -3
- package/dist/esm/memory/stages/pickByBudget.js.map +1 -1
- package/dist/esm/memory/stages/types.d.ts +15 -0
- package/dist/esm/memory/wire/mountMemoryPipeline.d.ts +25 -0
- package/dist/esm/memory/wire/mountMemoryPipeline.js +11 -2
- package/dist/esm/memory/wire/mountMemoryPipeline.js.map +1 -1
- package/dist/events/registry.js +2 -0
- package/dist/events/registry.js.map +1 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/fnv1a.js +28 -0
- package/dist/lib/fnv1a.js.map +1 -0
- package/dist/lib/injection-engine/types.js.map +1 -1
- package/dist/lib/rag/defineRAG.js +113 -26
- package/dist/lib/rag/defineRAG.js.map +1 -1
- package/dist/lib/rag/index.js +2 -1
- package/dist/lib/rag/index.js.map +1 -1
- package/dist/memory/define.js +38 -2
- package/dist/memory/define.js.map +1 -1
- package/dist/memory/define.types.js +21 -1
- package/dist/memory/define.types.js.map +1 -1
- package/dist/memory/embedding/loadRelevant.js +138 -8
- package/dist/memory/embedding/loadRelevant.js.map +1 -1
- package/dist/memory/index.js +5 -1
- package/dist/memory/index.js.map +1 -1
- package/dist/memory/pipeline/semantic.js +2 -0
- package/dist/memory/pipeline/semantic.js.map +1 -1
- package/dist/memory/retrieval/index.js +9 -0
- package/dist/memory/retrieval/index.js.map +1 -0
- package/dist/memory/retrieval/provenance.js +65 -0
- package/dist/memory/retrieval/provenance.js.map +1 -0
- package/dist/memory/retrieval/topK.js +57 -0
- package/dist/memory/retrieval/topK.js.map +1 -0
- package/dist/memory/retrieval/types.js +3 -0
- package/dist/memory/retrieval/types.js.map +1 -0
- package/dist/memory/stages/formatDefault.js +118 -28
- package/dist/memory/stages/formatDefault.js.map +1 -1
- package/dist/memory/stages/pickByBudget.js +53 -3
- package/dist/memory/stages/pickByBudget.js.map +1 -1
- package/dist/memory/wire/mountMemoryPipeline.js +11 -2
- package/dist/memory/wire/mountMemoryPipeline.js.map +1 -1
- package/dist/types/core/agent/buildAgentChart.d.ts.map +1 -1
- package/dist/types/core/agent/buildDynamicAgentChart.d.ts.map +1 -1
- package/dist/types/core/agent/memoryRecallInjections.d.ts +13 -3
- package/dist/types/core/agent/memoryRecallInjections.d.ts.map +1 -1
- package/dist/types/core/agent/stages/deliver.d.ts +6 -3
- package/dist/types/core/agent/stages/deliver.d.ts.map +1 -1
- package/dist/types/core/slots/buildSystemPromptSlot.d.ts.map +1 -1
- package/dist/types/core/slots/helpers.d.ts +11 -2
- package/dist/types/core/slots/helpers.d.ts.map +1 -1
- package/dist/types/events/payloads.d.ts +63 -0
- package/dist/types/events/payloads.d.ts.map +1 -1
- package/dist/types/events/registry.d.ts +3 -1
- package/dist/types/events/registry.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/lib/fnv1a.d.ts +17 -0
- package/dist/types/lib/fnv1a.d.ts.map +1 -0
- package/dist/types/lib/injection-engine/types.d.ts +17 -0
- package/dist/types/lib/injection-engine/types.d.ts.map +1 -1
- package/dist/types/lib/rag/defineRAG.d.ts +126 -26
- package/dist/types/lib/rag/defineRAG.d.ts.map +1 -1
- package/dist/types/lib/rag/index.d.ts +1 -1
- package/dist/types/lib/rag/index.d.ts.map +1 -1
- package/dist/types/memory/define.d.ts.map +1 -1
- package/dist/types/memory/define.types.d.ts +93 -1
- package/dist/types/memory/define.types.d.ts.map +1 -1
- package/dist/types/memory/embedding/loadRelevant.d.ts +49 -15
- package/dist/types/memory/embedding/loadRelevant.d.ts.map +1 -1
- package/dist/types/memory/index.d.ts +2 -1
- package/dist/types/memory/index.d.ts.map +1 -1
- package/dist/types/memory/pipeline/semantic.d.ts +15 -0
- package/dist/types/memory/pipeline/semantic.d.ts.map +1 -1
- package/dist/types/memory/retrieval/index.d.ts +11 -0
- package/dist/types/memory/retrieval/index.d.ts.map +1 -0
- package/dist/types/memory/retrieval/provenance.d.ts +44 -0
- package/dist/types/memory/retrieval/provenance.d.ts.map +1 -0
- package/dist/types/memory/retrieval/topK.d.ts +68 -0
- package/dist/types/memory/retrieval/topK.d.ts.map +1 -0
- package/dist/types/memory/retrieval/types.d.ts +190 -0
- package/dist/types/memory/retrieval/types.d.ts.map +1 -0
- package/dist/types/memory/stages/formatDefault.d.ts +52 -26
- package/dist/types/memory/stages/formatDefault.d.ts.map +1 -1
- package/dist/types/memory/stages/pickByBudget.d.ts.map +1 -1
- package/dist/types/memory/stages/types.d.ts +15 -0
- package/dist/types/memory/stages/types.d.ts.map +1 -1
- package/dist/types/memory/wire/mountMemoryPipeline.d.ts +25 -0
- package/dist/types/memory/wire/mountMemoryPipeline.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
*/
|
|
38
38
|
import { flowChart } from 'footprintjs';
|
|
39
39
|
import { pickByBudget } from '../stages/pickByBudget.js';
|
|
40
|
-
import { formatDefault } from '../stages/formatDefault.js';
|
|
40
|
+
import { formatDefault, } from '../stages/formatDefault.js';
|
|
41
41
|
import { writeMessages } from '../stages/writeMessages.js';
|
|
42
42
|
import { loadRelevant } from '../embedding/loadRelevant.js';
|
|
43
43
|
import { embedMessages, } from '../embedding/embedMessages.js';
|
|
@@ -57,6 +57,7 @@ export function semanticPipeline(config) {
|
|
|
57
57
|
...(config.k !== undefined && { k: config.k }),
|
|
58
58
|
...(config.minScore !== undefined && { minScore: config.minScore }),
|
|
59
59
|
...(config.tiers && { tiers: config.tiers }),
|
|
60
|
+
...(config.retrieval !== undefined && { retrieval: config.retrieval }),
|
|
60
61
|
};
|
|
61
62
|
const pickConfig = {
|
|
62
63
|
...(config.reserveTokens !== undefined && { reserveTokens: config.reserveTokens }),
|
|
@@ -66,6 +67,7 @@ export function semanticPipeline(config) {
|
|
|
66
67
|
const formatConfig = {
|
|
67
68
|
...(config.formatHeader !== undefined && { header: config.formatHeader }),
|
|
68
69
|
...(config.formatFooter !== undefined && { footer: config.formatFooter }),
|
|
70
|
+
...(config.flavor !== undefined && { flavor: config.flavor }),
|
|
69
71
|
};
|
|
70
72
|
const embedConfig = {
|
|
71
73
|
embedder: config.embedder,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"semantic.js","sourceRoot":"","sources":["../../../../src/memory/pipeline/semantic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,OAAO,EAAE,YAAY,EAA2B,MAAM,2BAA2B,CAAC;AAClF,OAAO,
|
|
1
|
+
{"version":3,"file":"semantic.js","sourceRoot":"","sources":["../../../../src/memory/pipeline/semantic.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* memory/retrieval — what a retrieval considered, and the seam that
|
|
3
|
+
* decides what it keeps.
|
|
4
|
+
*
|
|
5
|
+
* @see ./types.ts the record + the strategy interface
|
|
6
|
+
* @see ./topK.ts the strategy 8.7.0 had, now written down as one
|
|
7
|
+
*/
|
|
8
|
+
export type { RetrievalEvidence, RetrievalRejectReason, RetrievalStrategy, RetrievalVerdict, RetrievedCandidate, ScoredCandidate, } from './types.js';
|
|
9
|
+
export { topK, type TopKOptions } from './topK.js';
|
|
10
|
+
export { chunkProvenance, chunkText, type ChunkProvenance } from './provenance.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/memory/retrieval/index.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,IAAI,EAAoB,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,SAAS,EAAwB,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* provenance — read a stored entry's coordinates back out of it.
|
|
3
|
+
*
|
|
4
|
+
* Pattern: pure projection.
|
|
5
|
+
* Role: memory/ layer. One place that knows which metadata keys mean
|
|
6
|
+
* "which document", "which page", "which section", so the
|
|
7
|
+
* retrieval record and the citation the model sees can never
|
|
8
|
+
* disagree about a chunk's origin.
|
|
9
|
+
* Emits: N/A.
|
|
10
|
+
*
|
|
11
|
+
* The keys are the ones `indexDocuments` already accepts on
|
|
12
|
+
* `RagDocument.metadata`, and the ones a document splitter will write.
|
|
13
|
+
* Nothing is invented: an entry that carries no metadata simply has no
|
|
14
|
+
* coordinates, and the record says so by omitting the fields rather than
|
|
15
|
+
* by guessing a filename from an id.
|
|
16
|
+
*/
|
|
17
|
+
/** The document coordinates a chunk can carry. All optional — absence is honest. */
|
|
18
|
+
export interface ChunkProvenance {
|
|
19
|
+
/** Which document this text came from. Read from `docUri`, else `source`. */
|
|
20
|
+
readonly docUri?: string;
|
|
21
|
+
/** Which page, for paginated formats. */
|
|
22
|
+
readonly page?: number;
|
|
23
|
+
/** Which section heading the splitter cut under. */
|
|
24
|
+
readonly heading?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Pull the coordinates out of a stored value.
|
|
28
|
+
*
|
|
29
|
+
* Accepts metadata at the value's `metadata` key (where `indexDocuments`
|
|
30
|
+
* puts it) or directly on the value, so a hand-built entry works too.
|
|
31
|
+
*/
|
|
32
|
+
export declare function chunkProvenance(value: unknown): ChunkProvenance;
|
|
33
|
+
/**
|
|
34
|
+
* The text a stored value carries.
|
|
35
|
+
*
|
|
36
|
+
* Two shapes reach the formatter through the same pipeline: a chat
|
|
37
|
+
* `Message` (`{ role, content }`) from conversation memory, and a
|
|
38
|
+
* document (`{ id, content, metadata }`) from `indexDocuments`. Both
|
|
39
|
+
* keep their text on `content`, which is why one accessor serves both —
|
|
40
|
+
* but only the message shape has a meaningful `role`, which is why the
|
|
41
|
+
* corpus formatter does not print one.
|
|
42
|
+
*/
|
|
43
|
+
export declare function chunkText(value: unknown): string;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* provenance — read a stored entry's coordinates back out of it.
|
|
3
|
+
*
|
|
4
|
+
* Pattern: pure projection.
|
|
5
|
+
* Role: memory/ layer. One place that knows which metadata keys mean
|
|
6
|
+
* "which document", "which page", "which section", so the
|
|
7
|
+
* retrieval record and the citation the model sees can never
|
|
8
|
+
* disagree about a chunk's origin.
|
|
9
|
+
* Emits: N/A.
|
|
10
|
+
*
|
|
11
|
+
* The keys are the ones `indexDocuments` already accepts on
|
|
12
|
+
* `RagDocument.metadata`, and the ones a document splitter will write.
|
|
13
|
+
* Nothing is invented: an entry that carries no metadata simply has no
|
|
14
|
+
* coordinates, and the record says so by omitting the fields rather than
|
|
15
|
+
* by guessing a filename from an id.
|
|
16
|
+
*/
|
|
17
|
+
function asRecord(value) {
|
|
18
|
+
return typeof value === 'object' && value !== null
|
|
19
|
+
? value
|
|
20
|
+
: undefined;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Pull the coordinates out of a stored value.
|
|
24
|
+
*
|
|
25
|
+
* Accepts metadata at the value's `metadata` key (where `indexDocuments`
|
|
26
|
+
* puts it) or directly on the value, so a hand-built entry works too.
|
|
27
|
+
*/
|
|
28
|
+
export function chunkProvenance(value) {
|
|
29
|
+
const root = asRecord(value);
|
|
30
|
+
if (!root)
|
|
31
|
+
return {};
|
|
32
|
+
const meta = asRecord(root['metadata']) ?? root;
|
|
33
|
+
const rawDoc = meta['docUri'] ?? meta['source'];
|
|
34
|
+
const docUri = typeof rawDoc === 'string' && rawDoc.length > 0 ? rawDoc : undefined;
|
|
35
|
+
const rawPage = meta['page'];
|
|
36
|
+
const page = typeof rawPage === 'number' && Number.isFinite(rawPage) ? rawPage : undefined;
|
|
37
|
+
const rawHeading = meta['heading'];
|
|
38
|
+
const heading = typeof rawHeading === 'string' && rawHeading.length > 0 ? rawHeading : undefined;
|
|
39
|
+
return {
|
|
40
|
+
...(docUri !== undefined && { docUri }),
|
|
41
|
+
...(page !== undefined && { page }),
|
|
42
|
+
...(heading !== undefined && { heading }),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The text a stored value carries.
|
|
47
|
+
*
|
|
48
|
+
* Two shapes reach the formatter through the same pipeline: a chat
|
|
49
|
+
* `Message` (`{ role, content }`) from conversation memory, and a
|
|
50
|
+
* document (`{ id, content, metadata }`) from `indexDocuments`. Both
|
|
51
|
+
* keep their text on `content`, which is why one accessor serves both —
|
|
52
|
+
* but only the message shape has a meaningful `role`, which is why the
|
|
53
|
+
* corpus formatter does not print one.
|
|
54
|
+
*/
|
|
55
|
+
export function chunkText(value) {
|
|
56
|
+
const root = asRecord(value);
|
|
57
|
+
const content = root?.['content'];
|
|
58
|
+
return typeof content === 'string' ? content : '';
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=provenance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provenance.js","sourceRoot":"","sources":["../../../../src/memory/retrieval/provenance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAYH,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAChD,CAAC,CAAE,KAAiC;QACpC,CAAC,CAAC,SAAS,CAAC;AAChB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,KAAc;IAC5C,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC7B,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,CAAC;IACrB,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,IAAI,CAAC;IAEhD,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,CAAC;IAChD,MAAM,MAAM,GAAG,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;IAEpF,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;IAC7B,MAAM,IAAI,GAAG,OAAO,OAAO,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;IAE3F,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC;IACnC,MAAM,OAAO,GAAG,OAAO,UAAU,KAAK,QAAQ,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC;IAEjG,OAAO;QACL,GAAG,CAAC,MAAM,KAAK,SAAS,IAAI,EAAE,MAAM,EAAE,CAAC;QACvC,GAAG,CAAC,IAAI,KAAK,SAAS,IAAI,EAAE,IAAI,EAAE,CAAC;QACnC,GAAG,CAAC,OAAO,KAAK,SAAS,IAAI,EAAE,OAAO,EAAE,CAAC;KAC1C,CAAC;AACJ,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,SAAS,CAAC,KAAc;IACtC,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC7B,MAAM,OAAO,GAAG,IAAI,EAAE,CAAC,SAAS,CAAC,CAAC;IAClC,OAAO,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;AACpD,CAAC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* topK — the retrieval strategy agentfootprint has always used, now
|
|
3
|
+
* written down as one.
|
|
4
|
+
*
|
|
5
|
+
* Pattern: Strategy (one of {@link RetrievalStrategy}).
|
|
6
|
+
* Role: memory/ layer. Extracted in 8.8.0 from the two numbers that
|
|
7
|
+
* used to live loose on `defineRAG` (`topK`, `threshold`), so
|
|
8
|
+
* that a different rule can be written without touching a stage.
|
|
9
|
+
* Emits: N/A — the stage that calls it does the emitting.
|
|
10
|
+
*
|
|
11
|
+
* Behaviour is byte-for-byte what 8.7.0 did: take the highest-scoring
|
|
12
|
+
* candidates that clear `threshold`, at most `k` of them, and inject
|
|
13
|
+
* nothing at all when none clear it.
|
|
14
|
+
*
|
|
15
|
+
* **The threshold is strict, and strict means silent-by-design becomes
|
|
16
|
+
* loud-by-record.** When nothing clears the floor, no context is
|
|
17
|
+
* injected — a weak match in the prompt makes a confident wrong answer
|
|
18
|
+
* more likely, not less. What 8.8.0 changes is that the near-misses are
|
|
19
|
+
* now IN the record with their scores, so "the agent answered from
|
|
20
|
+
* nothing" is a readable outcome instead of an absence.
|
|
21
|
+
*/
|
|
22
|
+
import type { RetrievalStrategy } from './types.js';
|
|
23
|
+
export interface TopKOptions {
|
|
24
|
+
/**
|
|
25
|
+
* How many chunks may reach the prompt. Default 3 — enough for more
|
|
26
|
+
* than one perspective, few enough that the middle of a long context
|
|
27
|
+
* does not swallow the answer.
|
|
28
|
+
*/
|
|
29
|
+
readonly k?: number;
|
|
30
|
+
/**
|
|
31
|
+
* Minimum similarity to admit, in the store's score space ([-1, 1]
|
|
32
|
+
* cosine for every shipped store). Default 0.7.
|
|
33
|
+
*
|
|
34
|
+
* 0.7 is a high bar for some embedders. Sentence-transformer relatives
|
|
35
|
+
* (`all-MiniLM-L6-v2` and family, which `localEmbedder` uses by
|
|
36
|
+
* default) often score 0.4–0.6 on genuinely relevant chunks; OpenAI
|
|
37
|
+
* `text-embedding-3-*` sits comfortably at 0.7. If retrievals come back
|
|
38
|
+
* empty, read the `agentfootprint.memory.retrieved` event: it now
|
|
39
|
+
* carries the rejected candidates and their scores, so the right
|
|
40
|
+
* threshold is a number you can see rather than one you guess.
|
|
41
|
+
*
|
|
42
|
+
* Pass `null` for no floor — every candidate up to `k` is admitted.
|
|
43
|
+
*/
|
|
44
|
+
readonly threshold?: number | null;
|
|
45
|
+
/**
|
|
46
|
+
* How many extra candidates to pull past `k` so that rejected ones can
|
|
47
|
+
* be reported. Default 10. Raising it costs one larger read and shows
|
|
48
|
+
* more near-misses; it can never change which candidates are admitted.
|
|
49
|
+
*/
|
|
50
|
+
readonly rejectWindow?: number;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Build the top-K strategy.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```ts
|
|
57
|
+
* import { defineRAG } from 'agentfootprint';
|
|
58
|
+
* import { topK } from 'agentfootprint/memory';
|
|
59
|
+
*
|
|
60
|
+
* const docs = defineRAG({
|
|
61
|
+
* id: 'product-docs',
|
|
62
|
+
* store, embedder,
|
|
63
|
+
* retrieval: topK({ k: 5, threshold: 0.55 }),
|
|
64
|
+
* });
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export declare function topK(options?: TopKOptions): RetrievalStrategy;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the top-K strategy.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { defineRAG } from 'agentfootprint';
|
|
7
|
+
* import { topK } from 'agentfootprint/memory';
|
|
8
|
+
*
|
|
9
|
+
* const docs = defineRAG({
|
|
10
|
+
* id: 'product-docs',
|
|
11
|
+
* store, embedder,
|
|
12
|
+
* retrieval: topK({ k: 5, threshold: 0.55 }),
|
|
13
|
+
* });
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export function topK(options = {}) {
|
|
17
|
+
const k = options.k ?? 3;
|
|
18
|
+
if (!Number.isInteger(k) || k < 1) {
|
|
19
|
+
throw new Error(`topK: \`k\` must be a positive integer — received ${String(options.k)}.`);
|
|
20
|
+
}
|
|
21
|
+
const rejectWindow = options.rejectWindow ?? 10;
|
|
22
|
+
if (!Number.isInteger(rejectWindow) || rejectWindow < 0) {
|
|
23
|
+
throw new Error(`topK: \`rejectWindow\` must be a non-negative integer — received ${String(options.rejectWindow)}.`);
|
|
24
|
+
}
|
|
25
|
+
const threshold = options.threshold === null ? undefined : options.threshold ?? 0.7;
|
|
26
|
+
if (threshold !== undefined && !Number.isFinite(threshold)) {
|
|
27
|
+
throw new Error(`topK: \`threshold\` must be a finite number or null — received ${String(options.threshold)}.`);
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
name: 'topK',
|
|
31
|
+
k,
|
|
32
|
+
...(threshold !== undefined && { threshold }),
|
|
33
|
+
rejectWindow,
|
|
34
|
+
select(pool) {
|
|
35
|
+
const verdicts = [];
|
|
36
|
+
let admitted = 0;
|
|
37
|
+
for (const candidate of pool) {
|
|
38
|
+
if (threshold !== undefined && candidate.score < threshold) {
|
|
39
|
+
verdicts.push({ admitted: false, reason: 'below-threshold' });
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (admitted >= k) {
|
|
43
|
+
verdicts.push({ admitted: false, reason: 'over-max-entries' });
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
admitted += 1;
|
|
47
|
+
verdicts.push({ admitted: true });
|
|
48
|
+
}
|
|
49
|
+
return verdicts;
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=topK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"topK.js","sourceRoot":"","sources":["../../../../src/memory/retrieval/topK.ts"],"names":[],"mappings":"AAqDA;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,IAAI,CAAC,UAAuB,EAAE;IAC5C,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC;IACzB,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,KAAK,CAAC,qDAAqD,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAC7F,CAAC;IACD,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC;IAChD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,YAAY,CAAC,IAAI,YAAY,GAAG,CAAC,EAAE,CAAC;QACxD,MAAM,IAAI,KAAK,CACb,oEAAoE,MAAM,CACxE,OAAO,CAAC,YAAY,CACrB,GAAG,CACL,CAAC;IACJ,CAAC;IACD,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,IAAI,GAAG,CAAC;IACpF,IAAI,SAAS,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QAC3D,MAAM,IAAI,KAAK,CACb,kEAAkE,MAAM,CACtE,OAAO,CAAC,SAAS,CAClB,GAAG,CACL,CAAC;IACJ,CAAC;IAED,OAAO;QACL,IAAI,EAAE,MAAM;QACZ,CAAC;QACD,GAAG,CAAC,SAAS,KAAK,SAAS,IAAI,EAAE,SAAS,EAAE,CAAC;QAC7C,YAAY;QACZ,MAAM,CAAC,IAAgC;YACrC,MAAM,QAAQ,GAAuB,EAAE,CAAC;YACxC,IAAI,QAAQ,GAAG,CAAC,CAAC;YACjB,KAAK,MAAM,SAAS,IAAI,IAAI,EAAE,CAAC;gBAC7B,IAAI,SAAS,KAAK,SAAS,IAAI,SAAS,CAAC,KAAK,GAAG,SAAS,EAAE,CAAC;oBAC3D,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,CAAC,CAAC;oBAC9D,SAAS;gBACX,CAAC;gBACD,IAAI,QAAQ,IAAI,CAAC,EAAE,CAAC;oBAClB,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC,CAAC;oBAC/D,SAAS;gBACX,CAAC;gBACD,QAAQ,IAAI,CAAC,CAAC;gBACd,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;YACpC,CAAC;YACD,OAAO,QAAQ,CAAC;QAClB,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* retrieval/types — the record a retrieval leaves behind, and the seam
|
|
3
|
+
* that decides which candidates reach the prompt.
|
|
4
|
+
*
|
|
5
|
+
* Pattern: Strategy (the seam) + Value objects (the record).
|
|
6
|
+
* Role: memory/ layer. The law this folder exists to keep is stated
|
|
7
|
+
* once, here, because every stage downstream implements a piece
|
|
8
|
+
* of it: **a retrieval must be able to say what it considered,
|
|
9
|
+
* not only what it used.**
|
|
10
|
+
* Emits: N/A (types only). `loadRelevant` emits
|
|
11
|
+
* `agentfootprint.memory.retrieved` from the record below;
|
|
12
|
+
* `formatDefault` emits `agentfootprint.memory.attached` per
|
|
13
|
+
* admitted chunk.
|
|
14
|
+
*
|
|
15
|
+
* Before 8.8.0 a retrieval computed a cosine score for every candidate
|
|
16
|
+
* and then threw all of them away one line later (`results.map(r =>
|
|
17
|
+
* r.entry)`). The prompt carried the passages; nothing carried the
|
|
18
|
+
* reason. "Why did the agent read this passage" had no answer in the
|
|
19
|
+
* recording, and "why did it NOT read that one" had no answer anywhere —
|
|
20
|
+
* a below-threshold candidate was filtered inside the store and never
|
|
21
|
+
* came back. {@link RetrievalEvidence} is what that answer is made of.
|
|
22
|
+
*/
|
|
23
|
+
import type { MemoryEntry } from '../entry/index.js';
|
|
24
|
+
/**
|
|
25
|
+
* Why a candidate did not reach the prompt. Every rejected candidate
|
|
26
|
+
* names one of these — a rejection without a reason is the silence this
|
|
27
|
+
* whole record exists to remove.
|
|
28
|
+
*/
|
|
29
|
+
export type RetrievalRejectReason =
|
|
30
|
+
/** Scored below the retriever's `threshold`. The quality floor refused it. */
|
|
31
|
+
'below-threshold'
|
|
32
|
+
/** Cleared the threshold, but the context-token budget had no room left. */
|
|
33
|
+
| 'over-budget'
|
|
34
|
+
/** Cleared the threshold and the budget, but the picker's `maxEntries` cap was full. */
|
|
35
|
+
| 'over-max-entries';
|
|
36
|
+
/**
|
|
37
|
+
* One candidate the retrieval considered — admitted or not.
|
|
38
|
+
*
|
|
39
|
+
* `rank` is the candidate's position by SCORE (1-based, descending),
|
|
40
|
+
* which is not necessarily the order it appears in the prompt: the
|
|
41
|
+
* budget picker admits by recency (see {@link RetrievalEvidence.selectionOrder}).
|
|
42
|
+
* Recording both is the point — a reader can see that the best-scoring
|
|
43
|
+
* chunk was admitted third, and know that was the picker's doing.
|
|
44
|
+
*/
|
|
45
|
+
export interface RetrievedCandidate {
|
|
46
|
+
/** The store entry's id. For an indexed corpus this is the chunk id. */
|
|
47
|
+
readonly id: string;
|
|
48
|
+
/** Similarity as the store reported it. Cosine ([-1, 1]) for every shipped store. */
|
|
49
|
+
readonly score: number;
|
|
50
|
+
/** 1-based position by score, descending, across the whole candidate pool. */
|
|
51
|
+
readonly rank: number;
|
|
52
|
+
/** Did this candidate's text reach the prompt? */
|
|
53
|
+
readonly admitted: boolean;
|
|
54
|
+
/** Present exactly when `admitted` is false. */
|
|
55
|
+
readonly reason?: RetrievalRejectReason;
|
|
56
|
+
/** Source document, when the indexed value carried one in its metadata. */
|
|
57
|
+
readonly docUri?: string;
|
|
58
|
+
/** Page number, when the loader knew one (PDFs). */
|
|
59
|
+
readonly page?: number;
|
|
60
|
+
/** Section heading, when the splitter knew one. */
|
|
61
|
+
readonly heading?: string;
|
|
62
|
+
/**
|
|
63
|
+
* The exact prompt bytes this chunk contributed, set by the formatter
|
|
64
|
+
* for admitted candidates. Joining every admitted candidate's fragment
|
|
65
|
+
* **in {@link promptPosition} order** with `\n\n` reproduces the
|
|
66
|
+
* injected message exactly — which is what lets one retrieval become
|
|
67
|
+
* one `InjectionRecord` PER CHUNK without changing a single byte the
|
|
68
|
+
* model sees.
|
|
69
|
+
*/
|
|
70
|
+
readonly promptFragment?: string;
|
|
71
|
+
/**
|
|
72
|
+
* Where this chunk sat in the injected message, 0-based.
|
|
73
|
+
*
|
|
74
|
+
* NOT the same as {@link rank}, and the difference is the honest part:
|
|
75
|
+
* `rank` is how well the chunk scored, `promptPosition` is where the
|
|
76
|
+
* budget picker put it. Under the default recency ordering the
|
|
77
|
+
* best-scoring chunk can land last — which is exactly the kind of thing
|
|
78
|
+
* a lost-in-the-middle investigation needs to be able to see, and which
|
|
79
|
+
* a record that only kept one of the two orders could not show.
|
|
80
|
+
*/
|
|
81
|
+
readonly promptPosition?: number;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Everything one retrieval knows about itself. Written to the memory
|
|
85
|
+
* subflow's scope by `loadRelevant`, refined by `pickByBudget` and
|
|
86
|
+
* `formatDefault`, and lifted to the PARENT scope by the read mount so
|
|
87
|
+
* it lands in the root commit log where a slice can reach it.
|
|
88
|
+
*/
|
|
89
|
+
export interface RetrievalEvidence {
|
|
90
|
+
/** The retriever's id (`defineRAG({ id })`). Stamped by the read mount. */
|
|
91
|
+
readonly memoryId?: string;
|
|
92
|
+
/**
|
|
93
|
+
* A stable hash of the query text — NOT the text. The query is already
|
|
94
|
+
* in the recording once (as `userMessage`); copying it into a second
|
|
95
|
+
* key would widen the exposure surface for no new information, and any
|
|
96
|
+
* redaction policy the host configured for the first copy would not
|
|
97
|
+
* know about the second.
|
|
98
|
+
*/
|
|
99
|
+
readonly queryHash: string;
|
|
100
|
+
/** How many chunks the retriever was willing to admit. */
|
|
101
|
+
readonly k: number;
|
|
102
|
+
/** The quality floor. Absent when the retriever set none. */
|
|
103
|
+
readonly threshold?: number;
|
|
104
|
+
/** The embedder id the query was produced with, when the caller declared one. */
|
|
105
|
+
readonly embedderId?: string;
|
|
106
|
+
/** Length of the query vector. Mixing two lengths in one store is a config bug. */
|
|
107
|
+
readonly dimensions?: number;
|
|
108
|
+
/** How the budget picker ordered the admitted set. See the note on `rank`. */
|
|
109
|
+
readonly selectionOrder: 'recency' | 'relevance';
|
|
110
|
+
/** How many candidates came back from the store. */
|
|
111
|
+
readonly consideredCount: number;
|
|
112
|
+
/** How many reached the prompt. */
|
|
113
|
+
readonly admittedCount: number;
|
|
114
|
+
/** `consideredCount - admittedCount`. */
|
|
115
|
+
readonly rejectedCount: number;
|
|
116
|
+
/**
|
|
117
|
+
* The candidates themselves, best-scoring first.
|
|
118
|
+
*
|
|
119
|
+
* `undefined` means this store could not tell us — see
|
|
120
|
+
* {@link candidatesOmittedReason}. It never means "there were none";
|
|
121
|
+
* that case is `[]` with `consideredCount: 0`.
|
|
122
|
+
*/
|
|
123
|
+
readonly candidates?: readonly RetrievedCandidate[];
|
|
124
|
+
/**
|
|
125
|
+
* Whether {@link candidates} is the complete set of candidates that
|
|
126
|
+
* existed, or only as far as the pool we asked for reached.
|
|
127
|
+
*
|
|
128
|
+
* `false` does NOT weaken the admitted set — see the proof in
|
|
129
|
+
* `loadRelevant`. It only means the REJECTED list is a sample: there
|
|
130
|
+
* may be further below-threshold entries we never saw.
|
|
131
|
+
*/
|
|
132
|
+
readonly candidatesComplete: boolean;
|
|
133
|
+
/** Present exactly when `candidates` is undefined. */
|
|
134
|
+
readonly candidatesOmittedReason?: string;
|
|
135
|
+
/**
|
|
136
|
+
* The store returned nothing at all for this namespace. Distinct from
|
|
137
|
+
* "everything scored below threshold" (`consideredCount > 0`), and the
|
|
138
|
+
* distinction is the whole diagnosis: an empty namespace almost always
|
|
139
|
+
* means the corpus was indexed somewhere else.
|
|
140
|
+
*/
|
|
141
|
+
readonly corpusEmpty: boolean;
|
|
142
|
+
/** The namespace that was searched, as a plain string, for the diagnosis above. */
|
|
143
|
+
readonly namespace?: string;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* The retrieval seam: given the candidates a store returned, decide
|
|
147
|
+
* which of them the prompt may have — and say why about each one.
|
|
148
|
+
*
|
|
149
|
+
* A strategy NEVER talks to the store and never embeds anything. It is
|
|
150
|
+
* handed a scored, score-descending pool and returns a verdict per
|
|
151
|
+
* candidate. That narrowness is what makes it composable: a re-ranker or
|
|
152
|
+
* a diversity selector is the same shape with a different body.
|
|
153
|
+
*
|
|
154
|
+
* Shipped: {@link topK}. Deliberately NOT shipped in 8.8.0, and named
|
|
155
|
+
* here so the destination is on record rather than implied — a
|
|
156
|
+
* cross-encoder `rerank(...)` and a maximal-marginal-relevance `mmr(...)`
|
|
157
|
+
* are additional adapters behind this same interface. Neither needs an
|
|
158
|
+
* engine change, a new stage, or a new event; both were left out because
|
|
159
|
+
* a re-ranker without a shipped re-ranking model is a config with nothing
|
|
160
|
+
* to configure.
|
|
161
|
+
*/
|
|
162
|
+
export interface RetrievalStrategy {
|
|
163
|
+
/** Stable name — appears in the recording and in refusal messages. */
|
|
164
|
+
readonly name: string;
|
|
165
|
+
/** How many candidates this strategy is willing to admit. */
|
|
166
|
+
readonly k: number;
|
|
167
|
+
/** The quality floor, when the strategy has one. */
|
|
168
|
+
readonly threshold?: number;
|
|
169
|
+
/**
|
|
170
|
+
* How many EXTRA candidates to pull past `k` purely so that rejected
|
|
171
|
+
* ones can be shown. Never affects which candidates are admitted.
|
|
172
|
+
*/
|
|
173
|
+
readonly rejectWindow: number;
|
|
174
|
+
/**
|
|
175
|
+
* Rule on a score-descending pool. Return one verdict per input, in
|
|
176
|
+
* the same order. Implementations must not reorder.
|
|
177
|
+
*/
|
|
178
|
+
select(pool: readonly ScoredCandidate[]): readonly RetrievalVerdict[];
|
|
179
|
+
}
|
|
180
|
+
/** One store result, as a strategy sees it. */
|
|
181
|
+
export interface ScoredCandidate {
|
|
182
|
+
readonly entry: MemoryEntry<unknown>;
|
|
183
|
+
readonly score: number;
|
|
184
|
+
}
|
|
185
|
+
/** A strategy's ruling on one candidate. */
|
|
186
|
+
export interface RetrievalVerdict {
|
|
187
|
+
readonly admitted: boolean;
|
|
188
|
+
readonly reason?: RetrievalRejectReason;
|
|
189
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/memory/retrieval/types.ts"],"names":[],"mappings":""}
|
|
@@ -1,44 +1,64 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* formatDefault — render picked entries into injection-ready messages.
|
|
3
3
|
*
|
|
4
|
-
* Reads from scope: `selected`
|
|
5
|
-
* Writes to scope: `formatted` (messages to inject into the LLM prompt)
|
|
4
|
+
* Reads from scope: `selected`, `retrieved`
|
|
5
|
+
* Writes to scope: `formatted` (messages to inject into the LLM prompt),
|
|
6
|
+
* `retrieved` (each admitted chunk's prompt fragment)
|
|
6
7
|
*
|
|
7
8
|
* Why a separate stage from the picker?
|
|
8
|
-
* Retrieval and presentation are orthogonal concerns
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* Retrieval and presentation are orthogonal concerns. A picker decides
|
|
10
|
+
* WHICH memories survive the budget; a formatter decides HOW they appear
|
|
11
|
+
* to the LLM. Consumers can swap either without touching the other. In
|
|
12
|
+
* research settings, format variations ("JSON envelope" vs "XML tags" vs
|
|
13
|
+
* "natural paragraphs") are worth ablating.
|
|
13
14
|
*
|
|
14
|
-
*
|
|
15
|
-
* One `system` message containing a citation-tagged block per entry:
|
|
15
|
+
* ─── Two flavors, because they are two different claims ────────────────
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
17
|
+
* `'memory'` (default, unchanged since 7.20.0) — recall from THIS
|
|
18
|
+
* conversation. Each entry is a turn, so it is rendered with the turn's
|
|
19
|
+
* role and number:
|
|
20
|
+
*
|
|
21
|
+
* Relevant context from prior conversations. Use when it helps answer the current turn.
|
|
22
|
+
*
|
|
23
|
+
* <memory role="user" turn="5" updated="2026-04-18T06:00:00Z">
|
|
24
|
+
* I live in San Francisco.
|
|
19
25
|
* </memory>
|
|
20
26
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
27
|
+
* `'rag'` (8.8.0) — retrieval from a document corpus. An indexed chunk
|
|
28
|
+
* has no role and no turn; it has a document, a position in it, and a
|
|
29
|
+
* similarity score. Printing `role="unknown" turn="0"` on a page of a
|
|
30
|
+
* PDF, which is what the memory shape did, told the model three things
|
|
31
|
+
* that were not true and withheld the one thing it needed to cite:
|
|
32
|
+
*
|
|
33
|
+
* Relevant passages retrieved from the document corpus. Cite the source id when you use one.
|
|
23
34
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* of "context injected by the application, not part of the conversation."
|
|
35
|
+
* <source id="refunds.md#3" doc="refunds.md" heading="Refund timing" score="0.81">
|
|
36
|
+
* Refunds are processed within 3 business days of approval.
|
|
37
|
+
* </source>
|
|
28
38
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
39
|
+
* Role chosen (both flavors): `system`. This is NOT the ongoing dialogue,
|
|
40
|
+
* it is context the application is adding. A `user` role would confuse
|
|
41
|
+
* turn-taking; `assistant` would be a false claim.
|
|
42
|
+
*
|
|
43
|
+
* Wrapping: entries are grouped into ONE system message rather than N.
|
|
44
|
+
* One message is easier for a model to reason about — and the record
|
|
45
|
+
* still resolves to one `InjectionRecord` PER chunk, because each chunk's
|
|
46
|
+
* exact bytes are kept on the retrieval record (`promptFragment`) and
|
|
47
|
+
* joining them with `\n\n` reproduces this message exactly.
|
|
32
48
|
*/
|
|
33
49
|
import type { TypedScope } from 'footprintjs';
|
|
34
50
|
import type { MemoryEntry } from '../entry/index.js';
|
|
35
51
|
import type { LLMMessage as Message } from '../../adapters/types.js';
|
|
36
52
|
import type { MemoryState } from './types.js';
|
|
53
|
+
import type { RetrievedCandidate } from '../retrieval/types.js';
|
|
54
|
+
/** Which claim the injected block is making about its entries. */
|
|
55
|
+
export type MemoryFormatFlavor = 'memory' | 'rag';
|
|
37
56
|
export interface FormatDefaultConfig {
|
|
38
57
|
/**
|
|
39
58
|
* Header prepended to the injected message. Explains to the LLM what
|
|
40
59
|
* follows and what it's for. Override if your app has specific phrasing
|
|
41
|
-
* guidance ("long-term memory" vs "user preferences", etc.).
|
|
60
|
+
* guidance ("long-term memory" vs "user preferences", etc.). Defaults
|
|
61
|
+
* per `flavor`.
|
|
42
62
|
*/
|
|
43
63
|
readonly header?: string;
|
|
44
64
|
/**
|
|
@@ -48,12 +68,18 @@ export interface FormatDefaultConfig {
|
|
|
48
68
|
*/
|
|
49
69
|
readonly footer?: string;
|
|
50
70
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
|
|
71
|
+
* Which rendering the entries get. Default `'memory'` — conversation
|
|
72
|
+
* recall, byte-identical to every release before 8.8.0. `defineRAG`
|
|
73
|
+
* sets `'rag'`.
|
|
74
|
+
*/
|
|
75
|
+
readonly flavor?: MemoryFormatFlavor;
|
|
76
|
+
/**
|
|
77
|
+
* Custom per-entry renderer. Receives the entry (and, for retrieval
|
|
78
|
+
* pipelines, its candidate record); returns the block string. Use for
|
|
79
|
+
* app-specific formatting: custom source attributions, hiding tier
|
|
80
|
+
* info, etc. Overrides `flavor`.
|
|
55
81
|
*/
|
|
56
|
-
readonly renderEntry?: (entry: MemoryEntry<Message
|
|
82
|
+
readonly renderEntry?: (entry: MemoryEntry<Message>, candidate?: RetrievedCandidate) => string;
|
|
57
83
|
/**
|
|
58
84
|
* When `true`, inject even if `selected` is empty (emits only header
|
|
59
85
|
* and footer). Usually NOT desired — an empty memory block is noise.
|