@framers/agentos 0.2.6 → 0.2.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.
- package/dist/ingest-router/IngestRouter.d.ts +72 -0
- package/dist/ingest-router/IngestRouter.d.ts.map +1 -0
- package/dist/ingest-router/IngestRouter.js +98 -0
- package/dist/ingest-router/IngestRouter.js.map +1 -0
- package/dist/ingest-router/classifier.d.ts +63 -0
- package/dist/ingest-router/classifier.d.ts.map +1 -0
- package/dist/ingest-router/classifier.js +111 -0
- package/dist/ingest-router/classifier.js.map +1 -0
- package/dist/ingest-router/costs.d.ts +48 -0
- package/dist/ingest-router/costs.d.ts.map +1 -0
- package/dist/ingest-router/costs.js +63 -0
- package/dist/ingest-router/costs.js.map +1 -0
- package/dist/ingest-router/dispatcher.d.ts +35 -0
- package/dist/ingest-router/dispatcher.d.ts.map +1 -0
- package/dist/ingest-router/dispatcher.js +32 -0
- package/dist/ingest-router/dispatcher.js.map +1 -0
- package/dist/ingest-router/index.d.ts +43 -0
- package/dist/ingest-router/index.d.ts.map +1 -0
- package/dist/ingest-router/index.js +37 -0
- package/dist/ingest-router/index.js.map +1 -0
- package/dist/ingest-router/routing-tables.d.ts +122 -0
- package/dist/ingest-router/routing-tables.d.ts.map +1 -0
- package/dist/ingest-router/routing-tables.js +145 -0
- package/dist/ingest-router/routing-tables.js.map +1 -0
- package/dist/ingest-router/select-strategy.d.ts +67 -0
- package/dist/ingest-router/select-strategy.d.ts.map +1 -0
- package/dist/ingest-router/select-strategy.js +100 -0
- package/dist/ingest-router/select-strategy.js.map +1 -0
- package/dist/memory-router/MemoryRouter.d.ts +195 -0
- package/dist/memory-router/MemoryRouter.d.ts.map +1 -0
- package/dist/memory-router/MemoryRouter.js +155 -0
- package/dist/memory-router/MemoryRouter.js.map +1 -0
- package/dist/memory-router/adaptive.d.ts +142 -0
- package/dist/memory-router/adaptive.d.ts.map +1 -0
- package/dist/memory-router/adaptive.js +202 -0
- package/dist/memory-router/adaptive.js.map +1 -0
- package/dist/memory-router/backend-costs.d.ts +67 -0
- package/dist/memory-router/backend-costs.d.ts.map +1 -0
- package/dist/memory-router/backend-costs.js +136 -0
- package/dist/memory-router/backend-costs.js.map +1 -0
- package/dist/memory-router/classifier.d.ts +169 -0
- package/dist/memory-router/classifier.d.ts.map +1 -0
- package/dist/memory-router/classifier.js +193 -0
- package/dist/memory-router/classifier.js.map +1 -0
- package/dist/memory-router/dispatcher.d.ts +115 -0
- package/dist/memory-router/dispatcher.d.ts.map +1 -0
- package/dist/memory-router/dispatcher.js +84 -0
- package/dist/memory-router/dispatcher.js.map +1 -0
- package/dist/memory-router/index.d.ts +126 -0
- package/dist/memory-router/index.d.ts.map +1 -0
- package/dist/memory-router/index.js +122 -0
- package/dist/memory-router/index.js.map +1 -0
- package/dist/memory-router/routing-tables.d.ts +125 -0
- package/dist/memory-router/routing-tables.d.ts.map +1 -0
- package/dist/memory-router/routing-tables.js +137 -0
- package/dist/memory-router/routing-tables.js.map +1 -0
- package/dist/memory-router/select-backend.d.ts +136 -0
- package/dist/memory-router/select-backend.d.ts.map +1 -0
- package/dist/memory-router/select-backend.js +210 -0
- package/dist/memory-router/select-backend.js.map +1 -0
- package/dist/multi-stage-guardrails/index.d.ts +190 -0
- package/dist/multi-stage-guardrails/index.d.ts.map +1 -0
- package/dist/multi-stage-guardrails/index.js +186 -0
- package/dist/multi-stage-guardrails/index.js.map +1 -0
- package/dist/read-router/ReadRouter.d.ts +58 -0
- package/dist/read-router/ReadRouter.d.ts.map +1 -0
- package/dist/read-router/ReadRouter.js +91 -0
- package/dist/read-router/ReadRouter.js.map +1 -0
- package/dist/read-router/classifier.d.ts +54 -0
- package/dist/read-router/classifier.d.ts.map +1 -0
- package/dist/read-router/classifier.js +104 -0
- package/dist/read-router/classifier.js.map +1 -0
- package/dist/read-router/costs.d.ts +23 -0
- package/dist/read-router/costs.d.ts.map +1 -0
- package/dist/read-router/costs.js +51 -0
- package/dist/read-router/costs.js.map +1 -0
- package/dist/read-router/dispatcher.d.ts +33 -0
- package/dist/read-router/dispatcher.d.ts.map +1 -0
- package/dist/read-router/dispatcher.js +29 -0
- package/dist/read-router/dispatcher.js.map +1 -0
- package/dist/read-router/index.d.ts +23 -0
- package/dist/read-router/index.d.ts.map +1 -0
- package/dist/read-router/index.js +17 -0
- package/dist/read-router/index.js.map +1 -0
- package/dist/read-router/routing-tables.d.ts +85 -0
- package/dist/read-router/routing-tables.d.ts.map +1 -0
- package/dist/read-router/routing-tables.js +79 -0
- package/dist/read-router/routing-tables.js.map +1 -0
- package/dist/read-router/select-strategy.d.ts +42 -0
- package/dist/read-router/select-strategy.d.ts.map +1 -0
- package/dist/read-router/select-strategy.js +92 -0
- package/dist/read-router/select-strategy.js.map +1 -0
- package/package.json +21 -1
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file classifier.ts
|
|
3
|
+
* @description The LLM-as-judge classifier that the {@link MemoryRouter}
|
|
4
|
+
* uses to pick a {@link MemoryQueryCategory} for each incoming query.
|
|
5
|
+
*
|
|
6
|
+
* The classifier is deliberately abstracted behind {@link IMemoryClassifier}
|
|
7
|
+
* so callers can swap:
|
|
8
|
+
* - the LLM client (any provider — OpenAI, Anthropic, local, mock) via
|
|
9
|
+
* the {@link IMemoryClassifierLLM} adapter interface,
|
|
10
|
+
* - the prompt variant (base vs few-shot) per-call,
|
|
11
|
+
* - the classifier implementation entirely (e.g. a keyword-matcher or a
|
|
12
|
+
* small custom ML model) by implementing {@link IMemoryClassifier}.
|
|
13
|
+
*
|
|
14
|
+
* The reference implementation, {@link LLMMemoryClassifier}, runs the
|
|
15
|
+
* gpt-5-mini-style cheap single-shot discriminator prompt and robustly
|
|
16
|
+
* parses the output, falling back to `multi-session` on unparseable
|
|
17
|
+
* responses (the safest default — multi-session routes cover cross-session
|
|
18
|
+
* synthesis which handles most misidentified question types gracefully).
|
|
19
|
+
*
|
|
20
|
+
* @module @framers/agentos/memory-router/classifier
|
|
21
|
+
*/
|
|
22
|
+
import { MEMORY_QUERY_CATEGORIES, } from './routing-tables.js';
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// Prompts
|
|
25
|
+
// ============================================================================
|
|
26
|
+
/**
|
|
27
|
+
* Base classifier prompt. Lists the six category tokens with one-sentence
|
|
28
|
+
* definitions and a few examples per category, then instructs the model
|
|
29
|
+
* to emit ONLY the bare category token.
|
|
30
|
+
*/
|
|
31
|
+
export const CLASSIFIER_SYSTEM_PROMPT = `You are classifying a memory-system question into one of six categories.
|
|
32
|
+
|
|
33
|
+
Return ONLY the category token (no explanation, no quotes, no punctuation).
|
|
34
|
+
|
|
35
|
+
Categories:
|
|
36
|
+
- single-session-user: the question asks about something the USER said, did, or stated in a specific past session. Answer is in one session. Examples: "What did I tell you about my favorite dessert?", "Where did I say I moved to last month?"
|
|
37
|
+
- single-session-assistant: the question asks about something the ASSISTANT said, generated, or recommended in a specific session. Answer is in one session. Examples: "What recipe did you suggest for the birthday party?", "What books did you recommend to me?"
|
|
38
|
+
- single-session-preference: the question asks about a preference the user stated in passing. Answer is in one session. Examples: "Do I prefer tea or coffee?", "What's my favorite type of movie?"
|
|
39
|
+
- knowledge-update: the question asks about current state where the answer EVOLVED across sessions (supersession). Examples: "What's my current job title?", "Where do I live now?", "What's my latest project?"
|
|
40
|
+
- multi-session: the question requires combining information from 2+ separate sessions. Examples: "How many different languages have I mentioned studying?", "Which authors did you recommend across our conversations?"
|
|
41
|
+
- temporal-reasoning: the question asks about the order, timing, or duration of events across time. Examples: "In what order did I visit the three countries?", "How many months ago did I start the new job?"`;
|
|
42
|
+
/**
|
|
43
|
+
* Few-shot variant of the classifier prompt. Adds explicit
|
|
44
|
+
* Question/Category pairs targeting confusion patterns observed in the
|
|
45
|
+
* gpt-5-mini base-prompt classifier on LongMemEval Tier A:
|
|
46
|
+
* - SSA confused as SSU (YOU-said vs I-said distinction)
|
|
47
|
+
* - SSP confused as SSA (preferences phrased like recommendations)
|
|
48
|
+
* - MS confused as KU (cross-session vs current-state)
|
|
49
|
+
*
|
|
50
|
+
* Used when {@link MemoryClassifierClassifyOptions.useFewShotPrompt} is true.
|
|
51
|
+
*/
|
|
52
|
+
export const CLASSIFIER_SYSTEM_PROMPT_FEWSHOT = `You are classifying a memory-system question into one of six categories.
|
|
53
|
+
|
|
54
|
+
Return ONLY the category token (no explanation, no quotes, no punctuation).
|
|
55
|
+
|
|
56
|
+
Categories:
|
|
57
|
+
- single-session-user: the question asks about something the USER said, did, or stated in a specific past session. Answer is in one session.
|
|
58
|
+
- single-session-assistant: the question asks about something the ASSISTANT said, generated, or recommended in a specific session. Answer is in one session.
|
|
59
|
+
- single-session-preference: the question asks about a preference the user stated in passing. Answer is in one session.
|
|
60
|
+
- knowledge-update: the question asks about current state where the answer EVOLVED across sessions (supersession). The user wants the LATEST value of an attribute that has changed over time.
|
|
61
|
+
- multi-session: the question requires combining information from 2+ separate sessions. Counting, listing, or aggregating items the user mentioned across sessions.
|
|
62
|
+
- temporal-reasoning: the question asks about the order, timing, or duration of events across time.
|
|
63
|
+
|
|
64
|
+
Examples:
|
|
65
|
+
|
|
66
|
+
Question: What did I tell you my favorite ice cream flavor was?
|
|
67
|
+
Category: single-session-user
|
|
68
|
+
|
|
69
|
+
Question: Where did I say I moved to last month?
|
|
70
|
+
Category: single-session-user
|
|
71
|
+
|
|
72
|
+
Question: What book did you recommend to me last week?
|
|
73
|
+
Category: single-session-assistant
|
|
74
|
+
|
|
75
|
+
Question: What recipe did you suggest for the birthday party?
|
|
76
|
+
Category: single-session-assistant
|
|
77
|
+
|
|
78
|
+
Question: Do I prefer working in the morning or evening?
|
|
79
|
+
Category: single-session-preference
|
|
80
|
+
|
|
81
|
+
Question: What's my favorite type of movie?
|
|
82
|
+
Category: single-session-preference
|
|
83
|
+
|
|
84
|
+
Question: What's my current job title?
|
|
85
|
+
Category: knowledge-update
|
|
86
|
+
|
|
87
|
+
Question: Where do I live now?
|
|
88
|
+
Category: knowledge-update
|
|
89
|
+
|
|
90
|
+
Question: How many different programming languages have I mentioned learning?
|
|
91
|
+
Category: multi-session
|
|
92
|
+
|
|
93
|
+
Question: Which authors have you recommended to me across our conversations?
|
|
94
|
+
Category: multi-session
|
|
95
|
+
|
|
96
|
+
Question: In what order did I visit the three European cities?
|
|
97
|
+
Category: temporal-reasoning
|
|
98
|
+
|
|
99
|
+
Question: How many weeks ago did I start the new job?
|
|
100
|
+
Category: temporal-reasoning`;
|
|
101
|
+
// ============================================================================
|
|
102
|
+
// Parser
|
|
103
|
+
// ============================================================================
|
|
104
|
+
/**
|
|
105
|
+
* Default fallback category used when the classifier's LLM output cannot
|
|
106
|
+
* be parsed into a known category token. multi-session is chosen because
|
|
107
|
+
* its routing target (OM-based cross-session synthesis under max-accuracy,
|
|
108
|
+
* canonical-hybrid under min-cost) degrades gracefully on most other
|
|
109
|
+
* question types.
|
|
110
|
+
*/
|
|
111
|
+
export const SAFE_FALLBACK_CATEGORY = 'multi-session';
|
|
112
|
+
/**
|
|
113
|
+
* Strips common LLM-output decorations so the parser can match the bare
|
|
114
|
+
* category token:
|
|
115
|
+
* - keeps only the first non-empty line,
|
|
116
|
+
* - strips common label prefixes ("category:", "type:", "answer:"),
|
|
117
|
+
* - strips surrounding quotes / backticks,
|
|
118
|
+
* - strips trailing sentence punctuation,
|
|
119
|
+
* - lower-cases the result.
|
|
120
|
+
*/
|
|
121
|
+
export function normalizeClassifierOutput(raw) {
|
|
122
|
+
// First non-empty line only — models occasionally emit multi-line explanations.
|
|
123
|
+
const lines = raw.split('\n');
|
|
124
|
+
let firstLine = '';
|
|
125
|
+
for (const ln of lines) {
|
|
126
|
+
if (ln.trim().length > 0) {
|
|
127
|
+
firstLine = ln;
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
let cleaned = firstLine.trim().toLowerCase();
|
|
132
|
+
cleaned = cleaned.replace(/^(category|type|answer|label|class)\s*[:\-=]\s*/, '');
|
|
133
|
+
cleaned = cleaned.replace(/^["'`]+|["'`]+$/g, '');
|
|
134
|
+
cleaned = cleaned.replace(/[.,;!?]+$/g, '');
|
|
135
|
+
return cleaned.trim();
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Parse a normalized classifier output into a known category token, or
|
|
139
|
+
* return the safe fallback if no match is found.
|
|
140
|
+
*/
|
|
141
|
+
export function parseClassifierOutput(raw) {
|
|
142
|
+
const cleaned = normalizeClassifierOutput(raw);
|
|
143
|
+
for (const token of MEMORY_QUERY_CATEGORIES) {
|
|
144
|
+
if (cleaned === token ||
|
|
145
|
+
cleaned.startsWith(`${token} `) ||
|
|
146
|
+
cleaned.startsWith(`${token}\n`)) {
|
|
147
|
+
return token;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return SAFE_FALLBACK_CATEGORY;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* The built-in LLM-based classifier. Runs the category-discrimination
|
|
154
|
+
* prompt on the configured LLM adapter and parses the response robustly.
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* ```ts
|
|
158
|
+
* import { LLMMemoryClassifier } from '../memory-router/index.js';
|
|
159
|
+
*
|
|
160
|
+
* const classifier = new LLMMemoryClassifier({
|
|
161
|
+
* llm: createOpenAIClassifierAdapter('gpt-5-mini'),
|
|
162
|
+
* });
|
|
163
|
+
* const { category } = await classifier.classify(
|
|
164
|
+
* "What's my current job title?",
|
|
165
|
+
* );
|
|
166
|
+
* // => { category: 'knowledge-update', tokensIn: 412, tokensOut: 4, model: 'gpt-5-mini-2025-08-07' }
|
|
167
|
+
* ```
|
|
168
|
+
*/
|
|
169
|
+
export class LLMMemoryClassifier {
|
|
170
|
+
constructor(options) {
|
|
171
|
+
this.llm = options.llm;
|
|
172
|
+
this.maxTokens = options.maxTokens ?? 16;
|
|
173
|
+
}
|
|
174
|
+
async classify(query, options) {
|
|
175
|
+
const system = options?.useFewShotPrompt
|
|
176
|
+
? CLASSIFIER_SYSTEM_PROMPT_FEWSHOT
|
|
177
|
+
: CLASSIFIER_SYSTEM_PROMPT;
|
|
178
|
+
const user = `Question: ${query}\n\nCategory:`;
|
|
179
|
+
const response = await this.llm.invoke({
|
|
180
|
+
system,
|
|
181
|
+
user,
|
|
182
|
+
maxTokens: this.maxTokens,
|
|
183
|
+
temperature: 0,
|
|
184
|
+
});
|
|
185
|
+
return {
|
|
186
|
+
category: parseClassifierOutput(response.text),
|
|
187
|
+
tokensIn: response.tokensIn,
|
|
188
|
+
tokensOut: response.tokensOut,
|
|
189
|
+
model: response.model,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=classifier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classifier.js","sourceRoot":"","sources":["../../src/memory-router/classifier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EACL,uBAAuB,GAExB,MAAM,qBAAqB,CAAC;AA4F7B,+EAA+E;AAC/E,UAAU;AACV,+EAA+E;AAE/E;;;;GAIG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG;;;;;;;;;;+MAUuK,CAAC;AAEhN;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,gCAAgC,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6BAgDnB,CAAC;AAE9B,+EAA+E;AAC/E,SAAS;AACT,+EAA+E;AAE/E;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAwB,eAAe,CAAC;AAE3E;;;;;;;;GAQG;AACH,MAAM,UAAU,yBAAyB,CAAC,GAAW;IACnD,gFAAgF;IAChF,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC9B,IAAI,SAAS,GAAG,EAAE,CAAC;IACnB,KAAK,MAAM,EAAE,IAAI,KAAK,EAAE,CAAC;QACvB,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzB,SAAS,GAAG,EAAE,CAAC;YACf,MAAM;QACR,CAAC;IACH,CAAC;IACD,IAAI,OAAO,GAAG,SAAS,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC7C,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,iDAAiD,EAAE,EAAE,CAAC,CAAC;IACjF,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAAC;IAClD,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;IAC5C,OAAO,OAAO,CAAC,IAAI,EAAE,CAAC;AACxB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,GAAW;IAC/C,MAAM,OAAO,GAAG,yBAAyB,CAAC,GAAG,CAAC,CAAC;IAC/C,KAAK,MAAM,KAAK,IAAI,uBAAuB,EAAE,CAAC;QAC5C,IACE,OAAO,KAAK,KAAK;YACjB,OAAO,CAAC,UAAU,CAAC,GAAG,KAAK,GAAG,CAAC;YAC/B,OAAO,CAAC,UAAU,CAAC,GAAG,KAAK,IAAI,CAAC,EAChC,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IACD,OAAO,sBAAsB,CAAC;AAChC,CAAC;AAmBD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,OAAO,mBAAmB;IAI9B,YAAY,OAAmC;QAC7C,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;QACvB,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC;IAC3C,CAAC;IAED,KAAK,CAAC,QAAQ,CACZ,KAAa,EACb,OAAyC;QAEzC,MAAM,MAAM,GAAG,OAAO,EAAE,gBAAgB;YACtC,CAAC,CAAC,gCAAgC;YAClC,CAAC,CAAC,wBAAwB,CAAC;QAC7B,MAAM,IAAI,GAAG,aAAa,KAAK,eAAe,CAAC;QAE/C,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC;YACrC,MAAM;YACN,IAAI;YACJ,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,WAAW,EAAE,CAAC;SACf,CAAC,CAAC;QAEH,OAAO;YACL,QAAQ,EAAE,qBAAqB,CAAC,QAAQ,CAAC,IAAI,CAAC;YAC9C,QAAQ,EAAE,QAAQ,CAAC,QAAQ;YAC3B,SAAS,EAAE,QAAQ,CAAC,SAAS;YAC7B,KAAK,EAAE,QAAQ,CAAC,KAAK;SACtB,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file dispatcher.ts
|
|
3
|
+
* @description Backend-execution layer for {@link MemoryRouter}.
|
|
4
|
+
*
|
|
5
|
+
* A dispatcher turns a {@link MemoryBackendId} + a query into actual
|
|
6
|
+
* recall results. Because backend execution depends on how the caller's
|
|
7
|
+
* memory state is wired — `canonical-hybrid` needs only a query against a
|
|
8
|
+
* standing {@link Memory}, whereas `observational-memory-*` backends need
|
|
9
|
+
* ingest-time OM setup — the dispatcher is an injection point rather than
|
|
10
|
+
* a monolithic implementation.
|
|
11
|
+
*
|
|
12
|
+
* The shipping dispatcher, {@link FunctionMemoryDispatcher}, uses a
|
|
13
|
+
* routing-table-of-functions pattern: the caller provides `{ [backend]:
|
|
14
|
+
* (query, payload?) => Promise<traces> }` at construction, and the
|
|
15
|
+
* dispatcher picks the right function per call. This gives consumers:
|
|
16
|
+
* - full control over per-backend execution (connect to a standing
|
|
17
|
+
* HybridRetriever, a live OM ingest pipeline, a remote service, a
|
|
18
|
+
* cache, anything),
|
|
19
|
+
* - the ability to opt-out of backends they don't need (omitted keys
|
|
20
|
+
* raise a typed {@link UnsupportedMemoryBackendError} at dispatch
|
|
21
|
+
* time),
|
|
22
|
+
* - full type-safety on the per-call `payload` (passed through to the
|
|
23
|
+
* per-backend function verbatim).
|
|
24
|
+
*
|
|
25
|
+
* Callers who want to ship quickly with just canonical-hybrid can pass
|
|
26
|
+
* only `{ 'canonical-hybrid': (q) => mem.recall(q, { limit, policy }) }`
|
|
27
|
+
* and get end-to-end routing without touching the OM backends.
|
|
28
|
+
*
|
|
29
|
+
* @module @framers/agentos/memory-router/dispatcher
|
|
30
|
+
*/
|
|
31
|
+
import type { MemoryBackendId } from './routing-tables.js';
|
|
32
|
+
/**
|
|
33
|
+
* Per-backend execution function. Takes the query string + an optional
|
|
34
|
+
* caller-defined payload (e.g. topK, retrieval policy, session filter),
|
|
35
|
+
* returns the trace array.
|
|
36
|
+
*
|
|
37
|
+
* @typeParam TTrace - Shape of the trace the caller's memory layer emits.
|
|
38
|
+
* Defaults to the {@link ScoredTrace} shape from `@framers/agentos/memory`
|
|
39
|
+
* but any shape is accepted since the dispatcher is a pass-through.
|
|
40
|
+
* @typeParam TPayload - Shape of the optional payload argument.
|
|
41
|
+
*/
|
|
42
|
+
export type MemoryBackendExecutor<TTrace, TPayload = undefined> = (query: string, payload: TPayload) => Promise<TTrace[]>;
|
|
43
|
+
/**
|
|
44
|
+
* Args passed to {@link IMemoryDispatcher.dispatch}.
|
|
45
|
+
*/
|
|
46
|
+
export interface MemoryDispatchArgs<TPayload = undefined> {
|
|
47
|
+
readonly backend: MemoryBackendId;
|
|
48
|
+
readonly query: string;
|
|
49
|
+
/** Optional payload forwarded to the per-backend executor verbatim. */
|
|
50
|
+
readonly payload?: TPayload;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Result of a dispatch call. Carries the traces plus the backend that
|
|
54
|
+
* produced them (for telemetry + logging).
|
|
55
|
+
*/
|
|
56
|
+
export interface MemoryDispatchResult<TTrace> {
|
|
57
|
+
readonly traces: TTrace[];
|
|
58
|
+
readonly backend: MemoryBackendId;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The public dispatcher contract. Callers either use the built-in
|
|
62
|
+
* {@link FunctionMemoryDispatcher} or implement this interface with
|
|
63
|
+
* their own backend registry.
|
|
64
|
+
*/
|
|
65
|
+
export interface IMemoryDispatcher<TTrace = unknown, TPayload = unknown> {
|
|
66
|
+
dispatch(args: MemoryDispatchArgs<TPayload>): Promise<MemoryDispatchResult<TTrace>>;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Thrown when a dispatch call requests a backend that the dispatcher
|
|
70
|
+
* was not configured to support. Lets callers surface missing-backend
|
|
71
|
+
* bugs at the point of call rather than silently falling through.
|
|
72
|
+
*/
|
|
73
|
+
export declare class UnsupportedMemoryBackendError extends Error {
|
|
74
|
+
readonly backend: MemoryBackendId;
|
|
75
|
+
constructor(backend: MemoryBackendId);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Map of backend-id to executor function. Any subset of
|
|
79
|
+
* {@link MemoryBackendId} values may be registered; unregistered
|
|
80
|
+
* backends throw at dispatch time.
|
|
81
|
+
*/
|
|
82
|
+
export type MemoryBackendRegistry<TTrace, TPayload> = Partial<Record<MemoryBackendId, MemoryBackendExecutor<TTrace, TPayload>>>;
|
|
83
|
+
/**
|
|
84
|
+
* Built-in dispatcher that looks up a caller-supplied per-backend
|
|
85
|
+
* executor and invokes it with the query (+ optional payload).
|
|
86
|
+
*
|
|
87
|
+
* The generic parameters let each deployment type its trace shape and
|
|
88
|
+
* payload shape independently — a canonical-hybrid-only deployment can
|
|
89
|
+
* use `FunctionMemoryDispatcher<ScoredTrace, { topK: number }>`, while a
|
|
90
|
+
* mixed deployment can use `FunctionMemoryDispatcher<ScoredTrace, { topK:
|
|
91
|
+
* number; retrievalPolicy: MemoryRetrievalPolicy }>`.
|
|
92
|
+
*
|
|
93
|
+
* @example canonical-hybrid-only (simplest case)
|
|
94
|
+
* ```ts
|
|
95
|
+
* const dispatcher = new FunctionMemoryDispatcher<ScoredTrace, { topK: number }>({
|
|
96
|
+
* 'canonical-hybrid': async (query, { topK }) =>
|
|
97
|
+
* mem.recall(query, { limit: topK }),
|
|
98
|
+
* });
|
|
99
|
+
* ```
|
|
100
|
+
*
|
|
101
|
+
* @example Production routing with three backends
|
|
102
|
+
* ```ts
|
|
103
|
+
* const dispatcher = new FunctionMemoryDispatcher<ScoredTrace, RetrievalPayload>({
|
|
104
|
+
* 'canonical-hybrid': async (q, p) => hybridRetriever.retrieve(q, p),
|
|
105
|
+
* 'observational-memory-v10': async (q, p) => omPipeline.recall(q, p),
|
|
106
|
+
* 'observational-memory-v11': async (q, p) => omPipelineV11.recall(q, p),
|
|
107
|
+
* });
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
export declare class FunctionMemoryDispatcher<TTrace, TPayload = undefined> implements IMemoryDispatcher<TTrace, TPayload> {
|
|
111
|
+
private readonly registry;
|
|
112
|
+
constructor(registry: MemoryBackendRegistry<TTrace, TPayload>);
|
|
113
|
+
dispatch(args: MemoryDispatchArgs<TPayload>): Promise<MemoryDispatchResult<TTrace>>;
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=dispatcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/memory-router/dispatcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAM3D;;;;;;;;;GASG;AACH,MAAM,MAAM,qBAAqB,CAAC,MAAM,EAAE,QAAQ,GAAG,SAAS,IAAI,CAChE,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,QAAQ,KACd,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;AAEvB;;GAEG;AACH,MAAM,WAAW,kBAAkB,CAAC,QAAQ,GAAG,SAAS;IACtD,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,uEAAuE;IACvE,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC;CAC7B;AAED;;;GAGG;AACH,MAAM,WAAW,oBAAoB,CAAC,MAAM;IAC1C,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;CACnC;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB,CAAC,MAAM,GAAG,OAAO,EAAE,QAAQ,GAAG,OAAO;IACrE,QAAQ,CACN,IAAI,EAAE,kBAAkB,CAAC,QAAQ,CAAC,GACjC,OAAO,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC,CAAC;CAC1C;AAED;;;;GAIG;AACH,qBAAa,6BAA8B,SAAQ,KAAK;aAC1B,OAAO,EAAE,eAAe;gBAAxB,OAAO,EAAE,eAAe;CAOrD;AAMD;;;;GAIG;AACH,MAAM,MAAM,qBAAqB,CAAC,MAAM,EAAE,QAAQ,IAAI,OAAO,CAC3D,MAAM,CAAC,eAAe,EAAE,qBAAqB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CACjE,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,qBAAa,wBAAwB,CAAC,MAAM,EAAE,QAAQ,GAAG,SAAS,CAChE,YAAW,iBAAiB,CAAC,MAAM,EAAE,QAAQ,CAAC;IAE9C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA0C;gBAEvD,QAAQ,EAAE,qBAAqB,CAAC,MAAM,EAAE,QAAQ,CAAC;IAIvD,QAAQ,CACZ,IAAI,EAAE,kBAAkB,CAAC,QAAQ,CAAC,GACjC,OAAO,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC;CAQzC"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file dispatcher.ts
|
|
3
|
+
* @description Backend-execution layer for {@link MemoryRouter}.
|
|
4
|
+
*
|
|
5
|
+
* A dispatcher turns a {@link MemoryBackendId} + a query into actual
|
|
6
|
+
* recall results. Because backend execution depends on how the caller's
|
|
7
|
+
* memory state is wired — `canonical-hybrid` needs only a query against a
|
|
8
|
+
* standing {@link Memory}, whereas `observational-memory-*` backends need
|
|
9
|
+
* ingest-time OM setup — the dispatcher is an injection point rather than
|
|
10
|
+
* a monolithic implementation.
|
|
11
|
+
*
|
|
12
|
+
* The shipping dispatcher, {@link FunctionMemoryDispatcher}, uses a
|
|
13
|
+
* routing-table-of-functions pattern: the caller provides `{ [backend]:
|
|
14
|
+
* (query, payload?) => Promise<traces> }` at construction, and the
|
|
15
|
+
* dispatcher picks the right function per call. This gives consumers:
|
|
16
|
+
* - full control over per-backend execution (connect to a standing
|
|
17
|
+
* HybridRetriever, a live OM ingest pipeline, a remote service, a
|
|
18
|
+
* cache, anything),
|
|
19
|
+
* - the ability to opt-out of backends they don't need (omitted keys
|
|
20
|
+
* raise a typed {@link UnsupportedMemoryBackendError} at dispatch
|
|
21
|
+
* time),
|
|
22
|
+
* - full type-safety on the per-call `payload` (passed through to the
|
|
23
|
+
* per-backend function verbatim).
|
|
24
|
+
*
|
|
25
|
+
* Callers who want to ship quickly with just canonical-hybrid can pass
|
|
26
|
+
* only `{ 'canonical-hybrid': (q) => mem.recall(q, { limit, policy }) }`
|
|
27
|
+
* and get end-to-end routing without touching the OM backends.
|
|
28
|
+
*
|
|
29
|
+
* @module @framers/agentos/memory-router/dispatcher
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* Thrown when a dispatch call requests a backend that the dispatcher
|
|
33
|
+
* was not configured to support. Lets callers surface missing-backend
|
|
34
|
+
* bugs at the point of call rather than silently falling through.
|
|
35
|
+
*/
|
|
36
|
+
export class UnsupportedMemoryBackendError extends Error {
|
|
37
|
+
constructor(backend) {
|
|
38
|
+
super(`MemoryDispatcher: backend '${backend}' is not registered. ` +
|
|
39
|
+
`Supply an executor for this backend at construction time.`);
|
|
40
|
+
this.backend = backend;
|
|
41
|
+
this.name = 'UnsupportedMemoryBackendError';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Built-in dispatcher that looks up a caller-supplied per-backend
|
|
46
|
+
* executor and invokes it with the query (+ optional payload).
|
|
47
|
+
*
|
|
48
|
+
* The generic parameters let each deployment type its trace shape and
|
|
49
|
+
* payload shape independently — a canonical-hybrid-only deployment can
|
|
50
|
+
* use `FunctionMemoryDispatcher<ScoredTrace, { topK: number }>`, while a
|
|
51
|
+
* mixed deployment can use `FunctionMemoryDispatcher<ScoredTrace, { topK:
|
|
52
|
+
* number; retrievalPolicy: MemoryRetrievalPolicy }>`.
|
|
53
|
+
*
|
|
54
|
+
* @example canonical-hybrid-only (simplest case)
|
|
55
|
+
* ```ts
|
|
56
|
+
* const dispatcher = new FunctionMemoryDispatcher<ScoredTrace, { topK: number }>({
|
|
57
|
+
* 'canonical-hybrid': async (query, { topK }) =>
|
|
58
|
+
* mem.recall(query, { limit: topK }),
|
|
59
|
+
* });
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* @example Production routing with three backends
|
|
63
|
+
* ```ts
|
|
64
|
+
* const dispatcher = new FunctionMemoryDispatcher<ScoredTrace, RetrievalPayload>({
|
|
65
|
+
* 'canonical-hybrid': async (q, p) => hybridRetriever.retrieve(q, p),
|
|
66
|
+
* 'observational-memory-v10': async (q, p) => omPipeline.recall(q, p),
|
|
67
|
+
* 'observational-memory-v11': async (q, p) => omPipelineV11.recall(q, p),
|
|
68
|
+
* });
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export class FunctionMemoryDispatcher {
|
|
72
|
+
constructor(registry) {
|
|
73
|
+
this.registry = registry;
|
|
74
|
+
}
|
|
75
|
+
async dispatch(args) {
|
|
76
|
+
const executor = this.registry[args.backend];
|
|
77
|
+
if (!executor) {
|
|
78
|
+
throw new UnsupportedMemoryBackendError(args.backend);
|
|
79
|
+
}
|
|
80
|
+
const traces = await executor(args.query, args.payload);
|
|
81
|
+
return { traces, backend: args.backend };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=dispatcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dispatcher.js","sourceRoot":"","sources":["../../src/memory-router/dispatcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAqDH;;;;GAIG;AACH,MAAM,OAAO,6BAA8B,SAAQ,KAAK;IACtD,YAA4B,OAAwB;QAClD,KAAK,CACH,8BAA8B,OAAO,uBAAuB;YAC1D,2DAA2D,CAC9D,CAAC;QAJwB,YAAO,GAAP,OAAO,CAAiB;QAKlD,IAAI,CAAC,IAAI,GAAG,+BAA+B,CAAC;IAC9C,CAAC;CACF;AAeD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,OAAO,wBAAwB;IAKnC,YAAY,QAAiD;QAC3D,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;IAED,KAAK,CAAC,QAAQ,CACZ,IAAkC;QAElC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC7C,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAI,6BAA6B,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACxD,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,OAAmB,CAAC,CAAC;QACpE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC;IAC3C,CAAC;CACF"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentOS MemoryRouter Module
|
|
3
|
+
*
|
|
4
|
+
* LLM-as-judge orchestrator that picks the best memory-recall architecture
|
|
5
|
+
* per query, with budget-aware dispatch across {canonical-hybrid,
|
|
6
|
+
* observational-memory-v10, observational-memory-v11} backends.
|
|
7
|
+
*
|
|
8
|
+
* **Architecture Overview:**
|
|
9
|
+
* ```
|
|
10
|
+
* ┌────────────────────────────────────────────────────────────────────┐
|
|
11
|
+
* │ MemoryRouter │
|
|
12
|
+
* │ Orchestrates classification + routing-table dispatch + optional │
|
|
13
|
+
* │ backend execution (via IMemoryDispatcher) │
|
|
14
|
+
* └────────────────────────────────────────────────────────────────────┘
|
|
15
|
+
* │
|
|
16
|
+
* ┌──────────────────────┼────────────────────────┐
|
|
17
|
+
* ▼ ▼ ▼
|
|
18
|
+
* ┌───────────────┐ ┌─────────────────┐ ┌───────────────────┐
|
|
19
|
+
* │ IMemoryClassi-│ │ selectBackend │ │ IMemoryDispatcher │
|
|
20
|
+
* │ fier │ │ (pure, budget- │ │ (optional exec) │
|
|
21
|
+
* │ (LLM judge) │ │ aware) │ │ │
|
|
22
|
+
* └───────────────┘ └─────────────────┘ └───────────────────┘
|
|
23
|
+
* │
|
|
24
|
+
* ┌─────────────────────┼─────────────────────┐
|
|
25
|
+
* ▼ ▼ ▼
|
|
26
|
+
* ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────┐
|
|
27
|
+
* │ canonical- │ │ observational- │ │ observational- │
|
|
28
|
+
* │ hybrid │ │ memory-v10 │ │ memory-v11 │
|
|
29
|
+
* │ (BM25 + dense │ │ (synth obs log │ │ (v10 + verbatim │
|
|
30
|
+
* │ + Cohere │ │ + dyn router) │ │ citation for │
|
|
31
|
+
* │ rerank) │ │ │ │ KU/SSU) │
|
|
32
|
+
* └─────────────────┘ └─────────────────┘ └─────────────────────┘
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* **Design principles:**
|
|
36
|
+
*
|
|
37
|
+
* 1. **Pure where possible.** `selectBackend` is a pure function: given a
|
|
38
|
+
* category + routing table + cost data, it produces a deterministic
|
|
39
|
+
* decision with no I/O. Suitable for use inside cache-key construction
|
|
40
|
+
* and hot dispatch loops.
|
|
41
|
+
*
|
|
42
|
+
* 2. **LLM-provider-agnostic.** The classifier talks to an adapter interface
|
|
43
|
+
* ({@link IMemoryClassifierLLM}) — there is NO SDK import inside this
|
|
44
|
+
* module. Wire any provider (OpenAI, Anthropic, local, mock) via the
|
|
45
|
+
* adapter.
|
|
46
|
+
*
|
|
47
|
+
* 3. **Dispatch is injected.** Backend execution depends on how the caller's
|
|
48
|
+
* memory state is wired (OM backends need ingest-time setup, canonical
|
|
49
|
+
* does not). The router decides; {@link IMemoryDispatcher} executes.
|
|
50
|
+
* Callers who only need canonical-hybrid can register one executor and
|
|
51
|
+
* ignore the others.
|
|
52
|
+
*
|
|
53
|
+
* 4. **Shipping presets.** Three routing tables (minimize-cost, balanced,
|
|
54
|
+
* maximize-accuracy) ship with costs calibrated from LongMemEval-S
|
|
55
|
+
* Phase B N=500. Consumers can override routing tables, cost-points, or
|
|
56
|
+
* per-category mappings for custom workloads.
|
|
57
|
+
*
|
|
58
|
+
* 5. **Budget-aware.** Optional per-query USD budget with three modes
|
|
59
|
+
* (hard / soft / cheapest-fallback) so production cost ceilings are
|
|
60
|
+
* enforceable without bespoke retry logic.
|
|
61
|
+
*
|
|
62
|
+
* @module @framers/agentos/memory-router
|
|
63
|
+
*
|
|
64
|
+
* @example Minimal usage: just decide, execute yourself.
|
|
65
|
+
* ```ts
|
|
66
|
+
* import {
|
|
67
|
+
* LLMMemoryClassifier,
|
|
68
|
+
* MemoryRouter,
|
|
69
|
+
* } from '../memory-router';
|
|
70
|
+
*
|
|
71
|
+
* const router = new MemoryRouter({
|
|
72
|
+
* classifier: new LLMMemoryClassifier({ llm: openaiAdapter }),
|
|
73
|
+
* preset: 'minimize-cost',
|
|
74
|
+
* });
|
|
75
|
+
*
|
|
76
|
+
* const { classifier, routing } = await router.decide(query);
|
|
77
|
+
* if (routing.chosenBackend === 'canonical-hybrid') {
|
|
78
|
+
* const traces = await mem.recall(query, { limit: 10 });
|
|
79
|
+
* // ...
|
|
80
|
+
* }
|
|
81
|
+
* ```
|
|
82
|
+
*
|
|
83
|
+
* @example Full pipeline: decide + dispatch.
|
|
84
|
+
* ```ts
|
|
85
|
+
* import {
|
|
86
|
+
* LLMMemoryClassifier,
|
|
87
|
+
* MemoryRouter,
|
|
88
|
+
* FunctionMemoryDispatcher,
|
|
89
|
+
* } from '../memory-router';
|
|
90
|
+
*
|
|
91
|
+
* const router = new MemoryRouter({
|
|
92
|
+
* classifier: new LLMMemoryClassifier({ llm: openaiAdapter }),
|
|
93
|
+
* preset: 'minimize-cost',
|
|
94
|
+
* budget: { perQueryUsd: 0.05, mode: 'cheapest-fallback' },
|
|
95
|
+
* dispatcher: new FunctionMemoryDispatcher<ScoredTrace, { topK: number }>({
|
|
96
|
+
* 'canonical-hybrid': async (q, { topK }) =>
|
|
97
|
+
* mem.recall(q, { limit: topK }),
|
|
98
|
+
* 'observational-memory-v10': async (q, p) =>
|
|
99
|
+
* await omPipelineV10.recall(q, p),
|
|
100
|
+
* 'observational-memory-v11': async (q, p) =>
|
|
101
|
+
* await omPipelineV11.recall(q, p),
|
|
102
|
+
* }),
|
|
103
|
+
* });
|
|
104
|
+
*
|
|
105
|
+
* const { decision, traces, backend } = await router.decideAndDispatch(
|
|
106
|
+
* query,
|
|
107
|
+
* { topK: 10 },
|
|
108
|
+
* );
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
export type { MemoryQueryCategory, MemoryBackendId, MemoryRouterPreset, RoutingTable, } from './routing-tables.js';
|
|
112
|
+
export { MEMORY_QUERY_CATEGORIES } from './routing-tables.js';
|
|
113
|
+
export type { MemoryBackendCostPoint } from './backend-costs.js';
|
|
114
|
+
export type { MemoryBudgetMode, MemoryRouterConfig, MemoryRoutingDecision, } from './select-backend.js';
|
|
115
|
+
export type { IMemoryClassifier, IMemoryClassifierLLM, MemoryClassifierLLMRequest, MemoryClassifierLLMResponse, MemoryClassifierClassifyOptions, MemoryClassifierResult, LLMMemoryClassifierOptions, } from './classifier.js';
|
|
116
|
+
export type { IMemoryDispatcher, MemoryDispatchArgs, MemoryDispatchResult, MemoryBackendExecutor, MemoryBackendRegistry, } from './dispatcher.js';
|
|
117
|
+
export type { MemoryBudgetPolicy, MemoryRouterOptions, MemoryRouterDecideOptions, MemoryRouterDecision, MemoryRouterDispatchedDecision, } from './MemoryRouter.js';
|
|
118
|
+
export { MINIMIZE_COST_TABLE, BALANCED_TABLE, MAXIMIZE_ACCURACY_TABLE, PRESET_TABLES, } from './routing-tables.js';
|
|
119
|
+
export { TIER_1_CANONICAL_COSTS, TIER_2A_V10_COSTS, TIER_2B_V11_COSTS, DEFAULT_MEMORY_BACKEND_COSTS, } from './backend-costs.js';
|
|
120
|
+
export { selectBackend, MemoryRouterUnknownCategoryError, MemoryRouterBudgetExceededError, } from './select-backend.js';
|
|
121
|
+
export { CLASSIFIER_SYSTEM_PROMPT, CLASSIFIER_SYSTEM_PROMPT_FEWSHOT, SAFE_FALLBACK_CATEGORY, LLMMemoryClassifier, normalizeClassifierOutput, parseClassifierOutput, } from './classifier.js';
|
|
122
|
+
export { FunctionMemoryDispatcher, UnsupportedMemoryBackendError, } from './dispatcher.js';
|
|
123
|
+
export { MemoryRouter, MemoryRouterDispatcherMissingError, } from './MemoryRouter.js';
|
|
124
|
+
export type { CalibrationSample, CalibrationCell, AggregatedCalibration, AdaptivePresetRule, SelectByPresetArgs, BuildAdaptiveRoutingTableArgs, AdaptiveMemoryRouterOptions, } from './adaptive.js';
|
|
125
|
+
export { aggregateCalibration, selectByPreset, buildAdaptiveRoutingTable, AdaptiveMemoryRouter, } from './adaptive.js';
|
|
126
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/memory-router/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6GG;AAMH,YAAY,EACV,mBAAmB,EACnB,eAAe,EACf,kBAAkB,EAClB,YAAY,GACb,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAE9D,YAAY,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAEjE,YAAY,EACV,gBAAgB,EAChB,kBAAkB,EAClB,qBAAqB,GACtB,MAAM,qBAAqB,CAAC;AAE7B,YAAY,EACV,iBAAiB,EACjB,oBAAoB,EACpB,0BAA0B,EAC1B,2BAA2B,EAC3B,+BAA+B,EAC/B,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,iBAAiB,CAAC;AAEzB,YAAY,EACV,iBAAiB,EACjB,kBAAkB,EAClB,oBAAoB,EACpB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,iBAAiB,CAAC;AAEzB,YAAY,EACV,kBAAkB,EAClB,mBAAmB,EACnB,yBAAyB,EACzB,oBAAoB,EACpB,8BAA8B,GAC/B,MAAM,mBAAmB,CAAC;AAM3B,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,uBAAuB,EACvB,aAAa,GACd,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,sBAAsB,EACtB,iBAAiB,EACjB,iBAAiB,EACjB,4BAA4B,GAC7B,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,aAAa,EACb,gCAAgC,EAChC,+BAA+B,GAChC,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,wBAAwB,EACxB,gCAAgC,EAChC,sBAAsB,EACtB,mBAAmB,EACnB,yBAAyB,EACzB,qBAAqB,GACtB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,wBAAwB,EACxB,6BAA6B,GAC9B,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,YAAY,EACZ,kCAAkC,GACnC,MAAM,mBAAmB,CAAC;AAM3B,YAAY,EACV,iBAAiB,EACjB,eAAe,EACf,qBAAqB,EACrB,kBAAkB,EAClB,kBAAkB,EAClB,6BAA6B,EAC7B,2BAA2B,GAC5B,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,oBAAoB,EACpB,cAAc,EACd,yBAAyB,EACzB,oBAAoB,GACrB,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentOS MemoryRouter Module
|
|
3
|
+
*
|
|
4
|
+
* LLM-as-judge orchestrator that picks the best memory-recall architecture
|
|
5
|
+
* per query, with budget-aware dispatch across {canonical-hybrid,
|
|
6
|
+
* observational-memory-v10, observational-memory-v11} backends.
|
|
7
|
+
*
|
|
8
|
+
* **Architecture Overview:**
|
|
9
|
+
* ```
|
|
10
|
+
* ┌────────────────────────────────────────────────────────────────────┐
|
|
11
|
+
* │ MemoryRouter │
|
|
12
|
+
* │ Orchestrates classification + routing-table dispatch + optional │
|
|
13
|
+
* │ backend execution (via IMemoryDispatcher) │
|
|
14
|
+
* └────────────────────────────────────────────────────────────────────┘
|
|
15
|
+
* │
|
|
16
|
+
* ┌──────────────────────┼────────────────────────┐
|
|
17
|
+
* ▼ ▼ ▼
|
|
18
|
+
* ┌───────────────┐ ┌─────────────────┐ ┌───────────────────┐
|
|
19
|
+
* │ IMemoryClassi-│ │ selectBackend │ │ IMemoryDispatcher │
|
|
20
|
+
* │ fier │ │ (pure, budget- │ │ (optional exec) │
|
|
21
|
+
* │ (LLM judge) │ │ aware) │ │ │
|
|
22
|
+
* └───────────────┘ └─────────────────┘ └───────────────────┘
|
|
23
|
+
* │
|
|
24
|
+
* ┌─────────────────────┼─────────────────────┐
|
|
25
|
+
* ▼ ▼ ▼
|
|
26
|
+
* ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────┐
|
|
27
|
+
* │ canonical- │ │ observational- │ │ observational- │
|
|
28
|
+
* │ hybrid │ │ memory-v10 │ │ memory-v11 │
|
|
29
|
+
* │ (BM25 + dense │ │ (synth obs log │ │ (v10 + verbatim │
|
|
30
|
+
* │ + Cohere │ │ + dyn router) │ │ citation for │
|
|
31
|
+
* │ rerank) │ │ │ │ KU/SSU) │
|
|
32
|
+
* └─────────────────┘ └─────────────────┘ └─────────────────────┘
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* **Design principles:**
|
|
36
|
+
*
|
|
37
|
+
* 1. **Pure where possible.** `selectBackend` is a pure function: given a
|
|
38
|
+
* category + routing table + cost data, it produces a deterministic
|
|
39
|
+
* decision with no I/O. Suitable for use inside cache-key construction
|
|
40
|
+
* and hot dispatch loops.
|
|
41
|
+
*
|
|
42
|
+
* 2. **LLM-provider-agnostic.** The classifier talks to an adapter interface
|
|
43
|
+
* ({@link IMemoryClassifierLLM}) — there is NO SDK import inside this
|
|
44
|
+
* module. Wire any provider (OpenAI, Anthropic, local, mock) via the
|
|
45
|
+
* adapter.
|
|
46
|
+
*
|
|
47
|
+
* 3. **Dispatch is injected.** Backend execution depends on how the caller's
|
|
48
|
+
* memory state is wired (OM backends need ingest-time setup, canonical
|
|
49
|
+
* does not). The router decides; {@link IMemoryDispatcher} executes.
|
|
50
|
+
* Callers who only need canonical-hybrid can register one executor and
|
|
51
|
+
* ignore the others.
|
|
52
|
+
*
|
|
53
|
+
* 4. **Shipping presets.** Three routing tables (minimize-cost, balanced,
|
|
54
|
+
* maximize-accuracy) ship with costs calibrated from LongMemEval-S
|
|
55
|
+
* Phase B N=500. Consumers can override routing tables, cost-points, or
|
|
56
|
+
* per-category mappings for custom workloads.
|
|
57
|
+
*
|
|
58
|
+
* 5. **Budget-aware.** Optional per-query USD budget with three modes
|
|
59
|
+
* (hard / soft / cheapest-fallback) so production cost ceilings are
|
|
60
|
+
* enforceable without bespoke retry logic.
|
|
61
|
+
*
|
|
62
|
+
* @module @framers/agentos/memory-router
|
|
63
|
+
*
|
|
64
|
+
* @example Minimal usage: just decide, execute yourself.
|
|
65
|
+
* ```ts
|
|
66
|
+
* import {
|
|
67
|
+
* LLMMemoryClassifier,
|
|
68
|
+
* MemoryRouter,
|
|
69
|
+
* } from '../memory-router';
|
|
70
|
+
*
|
|
71
|
+
* const router = new MemoryRouter({
|
|
72
|
+
* classifier: new LLMMemoryClassifier({ llm: openaiAdapter }),
|
|
73
|
+
* preset: 'minimize-cost',
|
|
74
|
+
* });
|
|
75
|
+
*
|
|
76
|
+
* const { classifier, routing } = await router.decide(query);
|
|
77
|
+
* if (routing.chosenBackend === 'canonical-hybrid') {
|
|
78
|
+
* const traces = await mem.recall(query, { limit: 10 });
|
|
79
|
+
* // ...
|
|
80
|
+
* }
|
|
81
|
+
* ```
|
|
82
|
+
*
|
|
83
|
+
* @example Full pipeline: decide + dispatch.
|
|
84
|
+
* ```ts
|
|
85
|
+
* import {
|
|
86
|
+
* LLMMemoryClassifier,
|
|
87
|
+
* MemoryRouter,
|
|
88
|
+
* FunctionMemoryDispatcher,
|
|
89
|
+
* } from '../memory-router';
|
|
90
|
+
*
|
|
91
|
+
* const router = new MemoryRouter({
|
|
92
|
+
* classifier: new LLMMemoryClassifier({ llm: openaiAdapter }),
|
|
93
|
+
* preset: 'minimize-cost',
|
|
94
|
+
* budget: { perQueryUsd: 0.05, mode: 'cheapest-fallback' },
|
|
95
|
+
* dispatcher: new FunctionMemoryDispatcher<ScoredTrace, { topK: number }>({
|
|
96
|
+
* 'canonical-hybrid': async (q, { topK }) =>
|
|
97
|
+
* mem.recall(q, { limit: topK }),
|
|
98
|
+
* 'observational-memory-v10': async (q, p) =>
|
|
99
|
+
* await omPipelineV10.recall(q, p),
|
|
100
|
+
* 'observational-memory-v11': async (q, p) =>
|
|
101
|
+
* await omPipelineV11.recall(q, p),
|
|
102
|
+
* }),
|
|
103
|
+
* });
|
|
104
|
+
*
|
|
105
|
+
* const { decision, traces, backend } = await router.decideAndDispatch(
|
|
106
|
+
* query,
|
|
107
|
+
* { topK: 10 },
|
|
108
|
+
* );
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
export { MEMORY_QUERY_CATEGORIES } from './routing-tables.js';
|
|
112
|
+
// ============================================================================
|
|
113
|
+
// Values
|
|
114
|
+
// ============================================================================
|
|
115
|
+
export { MINIMIZE_COST_TABLE, BALANCED_TABLE, MAXIMIZE_ACCURACY_TABLE, PRESET_TABLES, } from './routing-tables.js';
|
|
116
|
+
export { TIER_1_CANONICAL_COSTS, TIER_2A_V10_COSTS, TIER_2B_V11_COSTS, DEFAULT_MEMORY_BACKEND_COSTS, } from './backend-costs.js';
|
|
117
|
+
export { selectBackend, MemoryRouterUnknownCategoryError, MemoryRouterBudgetExceededError, } from './select-backend.js';
|
|
118
|
+
export { CLASSIFIER_SYSTEM_PROMPT, CLASSIFIER_SYSTEM_PROMPT_FEWSHOT, SAFE_FALLBACK_CATEGORY, LLMMemoryClassifier, normalizeClassifierOutput, parseClassifierOutput, } from './classifier.js';
|
|
119
|
+
export { FunctionMemoryDispatcher, UnsupportedMemoryBackendError, } from './dispatcher.js';
|
|
120
|
+
export { MemoryRouter, MemoryRouterDispatcherMissingError, } from './MemoryRouter.js';
|
|
121
|
+
export { aggregateCalibration, selectByPreset, buildAdaptiveRoutingTable, AdaptiveMemoryRouter, } from './adaptive.js';
|
|
122
|
+
//# sourceMappingURL=index.js.map
|