@rudderjs/ai 1.18.2 → 1.18.4
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/README.md +16 -11
- package/dist/budget-orm/index.d.ts +95 -1
- package/dist/budget-orm/index.d.ts.map +1 -1
- package/dist/budget-orm/index.js +176 -4
- package/dist/budget-orm/index.js.map +1 -1
- package/dist/commands/ai-eval.d.ts +97 -1
- package/dist/commands/ai-eval.d.ts.map +1 -1
- package/dist/commands/ai-eval.js +379 -4
- package/dist/commands/ai-eval.js.map +1 -1
- package/dist/commands/make-agent.d.ts +2 -1
- package/dist/commands/make-agent.d.ts.map +1 -1
- package/dist/commands/make-agent.js +22 -4
- package/dist/commands/make-agent.js.map +1 -1
- package/dist/conversation-orm/index.d.ts +115 -1
- package/dist/conversation-orm/index.d.ts.map +1 -1
- package/dist/conversation-orm/index.js +214 -4
- package/dist/conversation-orm/index.js.map +1 -1
- package/dist/doctor.d.ts +1 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +67 -4
- package/dist/doctor.js.map +1 -1
- package/dist/memory-embedding/index.d.ts +120 -1
- package/dist/memory-embedding/index.d.ts.map +1 -1
- package/dist/memory-embedding/index.js +228 -4
- package/dist/memory-embedding/index.js.map +1 -1
- package/dist/memory-orm/index.d.ts +117 -1
- package/dist/memory-orm/index.d.ts.map +1 -1
- package/dist/memory-orm/index.js +186 -4
- package/dist/memory-orm/index.js.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +5 -4
- package/dist/server/index.js.map +1 -1
- package/dist/server/provider.d.ts +22 -0
- package/dist/server/provider.d.ts.map +1 -0
- package/dist/server/provider.js +179 -0
- package/dist/server/provider.js.map +1 -0
- package/package.json +12 -6
|
@@ -1,5 +1,215 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
/**
|
|
2
|
+
* `@rudderjs/ai/conversation-orm` - ORM-backed {@link ConversationStore}.
|
|
3
|
+
*
|
|
4
|
+
* Production-grade replacement for `MemoryConversationStore` (which is
|
|
5
|
+
* single-process, in-memory, and loses every thread on restart). Persists
|
|
6
|
+
* conversation threads and their messages via the registered `@rudderjs/orm`
|
|
7
|
+
* adapter - works across web processes, queue workers, and horizontally
|
|
8
|
+
* scaled deployments. Mirrors the `@rudderjs/ai/memory-orm` /
|
|
9
|
+
* `@rudderjs/ai/budget-orm` pattern.
|
|
10
|
+
*
|
|
11
|
+
* Wire it as the conversation store:
|
|
12
|
+
*
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { setConversationStore } from '@gemstack/ai-sdk'
|
|
15
|
+
* import { OrmConversationStore } from '@rudderjs/ai/conversation-orm'
|
|
16
|
+
*
|
|
17
|
+
* setConversationStore(new OrmConversationStore())
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* The schema lives at {@link conversationOrmPrismaSchema} - copy it into your
|
|
21
|
+
* Prisma schema (or a new `prisma/schema/<file>.prisma` if you use the
|
|
22
|
+
* multi-file setup). On the native engine, add an equivalent migration; on
|
|
23
|
+
* Drizzle, define matching tables and register them via `tables: { ... }`.
|
|
24
|
+
*
|
|
25
|
+
* # Adapter coverage
|
|
26
|
+
*
|
|
27
|
+
* - Prisma - works out of the box; copy {@link conversationOrmPrismaSchema}.
|
|
28
|
+
* - Native - add a migration with the same columns.
|
|
29
|
+
* - Drizzle - define the two tables and register them on the `drizzle()`
|
|
30
|
+
* config.
|
|
31
|
+
*
|
|
32
|
+
* # Ordering & concurrency
|
|
33
|
+
*
|
|
34
|
+
* Messages carry a monotonic per-thread `position` so `load()` returns them
|
|
35
|
+
* in append order regardless of timestamp granularity. `append()` reads the
|
|
36
|
+
* current max position and assigns the next slots; like
|
|
37
|
+
* `OrmBudgetStorage.checkAndDebit`, the read-then-write is not isolated, so
|
|
38
|
+
* two concurrent appends to the SAME thread could collide on a position.
|
|
39
|
+
* Conversation threads are single-writer in practice (one user, one turn at
|
|
40
|
+
* a time), so this is a non-issue for typical apps. File an issue if you hit
|
|
41
|
+
* it; strict ordering needs a serializable transaction or a DB sequence.
|
|
42
|
+
*/
|
|
43
|
+
import { Model } from '@rudderjs/orm';
|
|
44
|
+
import { sanitizeConversation } from '@gemstack/ai-sdk';
|
|
45
|
+
// ─── ORM Models ───────────────────────────────────────────
|
|
46
|
+
/**
|
|
47
|
+
* The thread row backing {@link OrmConversationStore}. Exposed so apps that
|
|
48
|
+
* want their own queries (admin views, analytics) can use
|
|
49
|
+
* `AiConversationRecord.where(...).get()` directly.
|
|
50
|
+
*
|
|
51
|
+
* `userId` / `agent` mirror {@link ConversationStoreMeta} - `userId` scopes
|
|
52
|
+
* `list()`, `agent` carries the thread-segregation key the auto-persist
|
|
53
|
+
* machinery uses to keep one user's threads per agent class apart.
|
|
54
|
+
*/
|
|
55
|
+
export class AiConversationRecord extends Model {
|
|
56
|
+
static table = 'aiConversation';
|
|
57
|
+
static fillable = ['title', 'userId', 'agent', 'updatedAt'];
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* One message row in a thread. `content` and `toolCalls` are JSON-encoded
|
|
61
|
+
* strings (so a `string` content and a `ContentPart[]` content both
|
|
62
|
+
* round-trip through a portable `text` column); `position` orders them.
|
|
63
|
+
*/
|
|
64
|
+
export class AiConversationMessageRecord extends Model {
|
|
65
|
+
static table = 'aiConversationMessage';
|
|
66
|
+
static fillable = ['conversationId', 'position', 'role', 'content', 'toolCallId', 'toolCalls'];
|
|
67
|
+
}
|
|
68
|
+
// ─── ConversationStore adapter ────────────────────────────
|
|
69
|
+
/**
|
|
70
|
+
* {@link ConversationStore} implementation that persists rows to the
|
|
71
|
+
* registered ORM adapter. Designed for production use - the in-process
|
|
72
|
+
* `MemoryConversationStore` is for tests and dev.
|
|
73
|
+
*/
|
|
74
|
+
export class OrmConversationStore {
|
|
75
|
+
async create(title, meta) {
|
|
76
|
+
const data = { title: title ?? 'New conversation' };
|
|
77
|
+
if (meta?.userId !== undefined)
|
|
78
|
+
data['userId'] = meta.userId;
|
|
79
|
+
if (meta?.agent !== undefined)
|
|
80
|
+
data['agent'] = meta.agent;
|
|
81
|
+
const created = await AiConversationRecord.create(data);
|
|
82
|
+
return created.id;
|
|
83
|
+
}
|
|
84
|
+
async load(conversationId) {
|
|
85
|
+
await this.requireThread(conversationId);
|
|
86
|
+
const rows = await AiConversationMessageRecord
|
|
87
|
+
.where('conversationId', conversationId)
|
|
88
|
+
.orderBy('position', 'ASC')
|
|
89
|
+
.get();
|
|
90
|
+
// A thread persisted mid-turn (crash between the assistant row and its
|
|
91
|
+
// tool-result rows) would otherwise replay into a provider 400. Drop the
|
|
92
|
+
// incomplete turns at the load boundary so the history is replay-safe.
|
|
93
|
+
return sanitizeConversation(rows.map(rowToMessage));
|
|
94
|
+
}
|
|
95
|
+
async append(conversationId, messages) {
|
|
96
|
+
await this.requireThread(conversationId);
|
|
97
|
+
if (messages.length === 0)
|
|
98
|
+
return;
|
|
99
|
+
let position = await this.nextPosition(conversationId);
|
|
100
|
+
for (const message of messages) {
|
|
101
|
+
await AiConversationMessageRecord.create(messageToRow(conversationId, position, message));
|
|
102
|
+
position++;
|
|
103
|
+
}
|
|
104
|
+
await AiConversationRecord.where('id', conversationId).updateAll({ updatedAt: new Date() });
|
|
105
|
+
}
|
|
106
|
+
async setTitle(conversationId, title) {
|
|
107
|
+
const updated = await AiConversationRecord
|
|
108
|
+
.where('id', conversationId)
|
|
109
|
+
.updateAll({ title, updatedAt: new Date() });
|
|
110
|
+
if (!updated)
|
|
111
|
+
throw notFound(conversationId);
|
|
112
|
+
}
|
|
113
|
+
async list(userId) {
|
|
114
|
+
let q = AiConversationRecord.query();
|
|
115
|
+
if (userId != null)
|
|
116
|
+
q = q.where('userId', userId);
|
|
117
|
+
const rows = await q.orderBy('updatedAt', 'DESC').get();
|
|
118
|
+
return rows.map(rowToListEntry);
|
|
119
|
+
}
|
|
120
|
+
async delete(conversationId) {
|
|
121
|
+
await AiConversationMessageRecord.where('conversationId', conversationId).deleteAll();
|
|
122
|
+
await AiConversationRecord.where('id', conversationId).deleteAll();
|
|
123
|
+
}
|
|
124
|
+
/** Throw the same not-found error shape as `MemoryConversationStore`. */
|
|
125
|
+
async requireThread(conversationId) {
|
|
126
|
+
const thread = await AiConversationRecord.where('id', conversationId).first();
|
|
127
|
+
if (!thread)
|
|
128
|
+
throw notFound(conversationId);
|
|
129
|
+
}
|
|
130
|
+
/** Next monotonic position for the thread (0 when empty). */
|
|
131
|
+
async nextPosition(conversationId) {
|
|
132
|
+
const last = await AiConversationMessageRecord
|
|
133
|
+
.where('conversationId', conversationId)
|
|
134
|
+
.orderBy('position', 'DESC')
|
|
135
|
+
.first();
|
|
136
|
+
return last ? last.position + 1 : 0;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/** Convenience factory mirroring `ormBudgetStorage()` / `OrmUserMemory`. */
|
|
140
|
+
export function ormConversationStore() {
|
|
141
|
+
return new OrmConversationStore();
|
|
142
|
+
}
|
|
143
|
+
// ─── Schema reference ─────────────────────────────────────
|
|
144
|
+
/**
|
|
145
|
+
* Reference Prisma schema for `OrmConversationStore`. Copy into your
|
|
146
|
+
* `prisma/schema/<file>.prisma`. SQLite stores the `text` content as TEXT;
|
|
147
|
+
* Postgres as `text`. The `@@index` keeps `list()` (by user) and `load()`
|
|
148
|
+
* (by thread, ordered) cheap.
|
|
149
|
+
*/
|
|
150
|
+
export const conversationOrmPrismaSchema = `model AiConversation {
|
|
151
|
+
id String @id @default(cuid())
|
|
152
|
+
title String
|
|
153
|
+
userId String?
|
|
154
|
+
/// Thread-segregation key - the agent class name by default
|
|
155
|
+
agent String?
|
|
156
|
+
createdAt DateTime @default(now())
|
|
157
|
+
updatedAt DateTime @updatedAt
|
|
158
|
+
|
|
159
|
+
@@index([userId])
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
model AiConversationMessage {
|
|
163
|
+
id String @id @default(cuid())
|
|
164
|
+
conversationId String
|
|
165
|
+
/// Monotonic per-thread ordering
|
|
166
|
+
position Int
|
|
167
|
+
role String
|
|
168
|
+
/// JSON-encoded \`string | ContentPart[]\`
|
|
169
|
+
content String
|
|
170
|
+
toolCallId String?
|
|
171
|
+
/// JSON-encoded \`ToolCall[]\` or null
|
|
172
|
+
toolCalls String?
|
|
173
|
+
createdAt DateTime @default(now())
|
|
174
|
+
|
|
175
|
+
@@index([conversationId, position])
|
|
176
|
+
}
|
|
177
|
+
`;
|
|
178
|
+
// ─── Helpers ──────────────────────────────────────────────
|
|
179
|
+
function notFound(conversationId) {
|
|
180
|
+
return new Error(`[ai-sdk] Conversation "${conversationId}" not found.`);
|
|
181
|
+
}
|
|
182
|
+
function messageToRow(conversationId, position, m) {
|
|
183
|
+
return {
|
|
184
|
+
conversationId,
|
|
185
|
+
position,
|
|
186
|
+
role: m.role,
|
|
187
|
+
content: JSON.stringify(m.content),
|
|
188
|
+
toolCallId: m.toolCallId ?? null,
|
|
189
|
+
toolCalls: m.toolCalls ? JSON.stringify(m.toolCalls) : null,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
function rowToMessage(row) {
|
|
193
|
+
const out = {
|
|
194
|
+
role: row.role,
|
|
195
|
+
content: JSON.parse(row.content),
|
|
196
|
+
};
|
|
197
|
+
if (row.toolCallId != null)
|
|
198
|
+
out.toolCallId = row.toolCallId;
|
|
199
|
+
if (row.toolCalls != null)
|
|
200
|
+
out.toolCalls = JSON.parse(row.toolCalls);
|
|
201
|
+
return out;
|
|
202
|
+
}
|
|
203
|
+
function rowToListEntry(row) {
|
|
204
|
+
const out = {
|
|
205
|
+
id: row.id,
|
|
206
|
+
title: row.title,
|
|
207
|
+
createdAt: row.createdAt,
|
|
208
|
+
};
|
|
209
|
+
if (row.updatedAt != null)
|
|
210
|
+
out.updatedAt = row.updatedAt;
|
|
211
|
+
if (row.agent != null)
|
|
212
|
+
out.agent = row.agent;
|
|
213
|
+
return out;
|
|
214
|
+
}
|
|
5
215
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/conversation-orm/index.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/conversation-orm/index.ts"],"names":[],"mappings":"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"}
|
package/dist/doctor.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export
|
|
1
|
+
export {};
|
|
2
2
|
//# sourceMappingURL=doctor.d.ts.map
|
package/dist/doctor.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":""}
|
package/dist/doctor.js
CHANGED
|
@@ -1,5 +1,68 @@
|
|
|
1
|
-
// @rudderjs/ai
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
|
|
1
|
+
// AI doctor checks contributed by @rudderjs/ai. This is a Rudder binding: it
|
|
2
|
+
// registers into @rudderjs/console's doctor registry, so it lives here rather
|
|
3
|
+
// than in the agnostic @gemstack/ai-sdk engine.
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { registerDoctorCheck } from '@rudderjs/console';
|
|
7
|
+
function readFileSafe(rel) {
|
|
8
|
+
try {
|
|
9
|
+
return fs.readFileSync(path.join(process.cwd(), rel), 'utf-8');
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
// Maps provider driver name → env var the user must set. Mirrors the
|
|
16
|
+
// driver names listed in @gemstack/ai-sdk's provider implementations.
|
|
17
|
+
const PROVIDER_ENV = {
|
|
18
|
+
anthropic: 'ANTHROPIC_API_KEY',
|
|
19
|
+
openai: 'OPENAI_API_KEY',
|
|
20
|
+
google: 'GOOGLE_AI_API_KEY',
|
|
21
|
+
bedrock: 'AWS_ACCESS_KEY_ID',
|
|
22
|
+
groq: 'GROQ_API_KEY',
|
|
23
|
+
openrouter: 'OPENROUTER_API_KEY',
|
|
24
|
+
// ollama, lmstudio: local — no key needed.
|
|
25
|
+
};
|
|
26
|
+
// Extracts driver names referenced by config/ai.ts WITHOUT importing the
|
|
27
|
+
// module. We grep for `driver: '<name>'` literals — covers the scaffolded
|
|
28
|
+
// shape.
|
|
29
|
+
function declaredProviders() {
|
|
30
|
+
const text = readFileSafe('config/ai.ts') ??
|
|
31
|
+
readFileSafe('config/ai.js') ??
|
|
32
|
+
readFileSafe('config/ai.mjs') ?? '';
|
|
33
|
+
const matches = [...text.matchAll(/driver\s*:\s*['"]([^'"]+)['"]/g)];
|
|
34
|
+
return [...new Set(matches.map(m => m[1]).filter(Boolean))];
|
|
35
|
+
}
|
|
36
|
+
registerDoctorCheck({
|
|
37
|
+
id: 'ai:provider-keys',
|
|
38
|
+
category: 'ai',
|
|
39
|
+
title: 'AI provider API keys',
|
|
40
|
+
run() {
|
|
41
|
+
const providers = declaredProviders();
|
|
42
|
+
if (providers.length === 0) {
|
|
43
|
+
return { status: 'ok', message: 'no config/ai.ts or no providers declared — skip' };
|
|
44
|
+
}
|
|
45
|
+
const needsKey = providers.filter(p => p in PROVIDER_ENV);
|
|
46
|
+
if (needsKey.length === 0) {
|
|
47
|
+
return { status: 'ok', message: `${providers.length} provider(s) — all local (no keys required)` };
|
|
48
|
+
}
|
|
49
|
+
const missing = needsKey.filter(p => !process.env[PROVIDER_ENV[p]]);
|
|
50
|
+
if (missing.length === needsKey.length) {
|
|
51
|
+
return {
|
|
52
|
+
status: 'warn',
|
|
53
|
+
message: `none of ${needsKey.length} cloud provider(s) have an API key set`,
|
|
54
|
+
fix: `Set at least one of: ${needsKey.map(p => PROVIDER_ENV[p]).join(', ')} (or remove the providers from config/ai.ts if unused)`,
|
|
55
|
+
detail: `Declared providers: ${needsKey.join(', ')}`,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (missing.length > 0) {
|
|
59
|
+
return {
|
|
60
|
+
status: 'warn',
|
|
61
|
+
message: `${needsKey.length - missing.length}/${needsKey.length} cloud provider(s) have keys`,
|
|
62
|
+
fix: `Set missing keys: ${missing.map(p => PROVIDER_ENV[p]).join(', ')} (or remove the providers from config/ai.ts if unused)`,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
return { status: 'ok', message: `${needsKey.length} cloud provider(s), all keys present` };
|
|
66
|
+
},
|
|
67
|
+
});
|
|
5
68
|
//# sourceMappingURL=doctor.js.map
|
package/dist/doctor.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,2 +1,121 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* `@rudderjs/ai/memory-embedding` — embedding-backed {@link UserMemory}
|
|
3
|
+
* for #A4 Phase 5.
|
|
4
|
+
*
|
|
5
|
+
* Composes Phase 4's {@link OrmUserMemory} with the embedding
|
|
6
|
+
* provider registered on {@link AiRegistry}: `remember()` embeds the
|
|
7
|
+
* fact and writes the Float32-packed vector into the row's
|
|
8
|
+
* `embedding` column; `recall()` embeds the query and ranks by
|
|
9
|
+
* cosine similarity. `forget()` / `forgetAll()` delegate to the
|
|
10
|
+
* inner store — the embedding lives in the same row, so deleting
|
|
11
|
+
* the row deletes the vector. GDPR right-to-be-forgotten cascades
|
|
12
|
+
* automatically.
|
|
13
|
+
*
|
|
14
|
+
* v1 is **pure-JS cosine over the user's full set** — fine up to
|
|
15
|
+
* a few thousand facts per user. For larger workloads, B7 lands a
|
|
16
|
+
* pgvector-backed `EmbeddingUserMemory` that pushes the dot-product
|
|
17
|
+
* into the database.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { OrmUserMemory } from '@rudderjs/ai/memory-orm'
|
|
22
|
+
* import { EmbeddingUserMemory } from '@rudderjs/ai/memory-embedding'
|
|
23
|
+
*
|
|
24
|
+
* const memory = new EmbeddingUserMemory({
|
|
25
|
+
* inner: new OrmUserMemory(),
|
|
26
|
+
* model: 'openai/text-embedding-3-small',
|
|
27
|
+
* threshold: 0.5, // cosine floor; matches below are dropped
|
|
28
|
+
* })
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* **Pre-Phase-5 facts** (rows with `embedding === null`) fall back to
|
|
32
|
+
* token-overlap matching against the `fact` column — same shape as
|
|
33
|
+
* `MemoryUserMemory.recall()`. So upgrading from `OrmUserMemory` to
|
|
34
|
+
* `EmbeddingUserMemory` doesn't lose recall on existing rows; new
|
|
35
|
+
* `remember()` calls populate the embedding column going forward.
|
|
36
|
+
*/
|
|
37
|
+
import { OrmUserMemory } from '../memory-orm/index.js';
|
|
38
|
+
import type { MemoryEntry, UserMemory } from '@gemstack/ai-sdk';
|
|
39
|
+
export interface EmbeddingUserMemoryOptions {
|
|
40
|
+
/**
|
|
41
|
+
* The composed inner store. Must be {@link OrmUserMemory} for v1
|
|
42
|
+
* — the composer reads/writes the `embedding Bytes?` column on
|
|
43
|
+
* the same row. Other backends (Pinecone, Weaviate) implement
|
|
44
|
+
* their own.
|
|
45
|
+
*/
|
|
46
|
+
inner: OrmUserMemory;
|
|
47
|
+
/**
|
|
48
|
+
* Embedding model id (`'<provider>/<model>'`). Used for both
|
|
49
|
+
* fact embedding on `remember()` and query embedding on
|
|
50
|
+
* `recall()`. Default: whatever `AI.embed()` picks (`AiRegistry`
|
|
51
|
+
* default).
|
|
52
|
+
*/
|
|
53
|
+
model?: string;
|
|
54
|
+
/**
|
|
55
|
+
* Cosine-similarity floor in `[-1, 1]`. Matches below the
|
|
56
|
+
* threshold are dropped before sorting. Default `0` — return
|
|
57
|
+
* everything ranked. Tighten for higher precision; loosen for
|
|
58
|
+
* higher recall.
|
|
59
|
+
*/
|
|
60
|
+
threshold?: number;
|
|
61
|
+
/**
|
|
62
|
+
* Optional fallback for rows whose `embedding` column is `null`
|
|
63
|
+
* (rows persisted without the embedding composer wired in).
|
|
64
|
+
*
|
|
65
|
+
* - `'token-overlap'` (default) — score 0 if any ≥3-char token
|
|
66
|
+
* from the query appears in the row's `fact`. Lets you
|
|
67
|
+
* upgrade `OrmUserMemory` → `EmbeddingUserMemory` without
|
|
68
|
+
* losing recall on existing rows.
|
|
69
|
+
* - `'skip'` — drop null-embedding rows entirely.
|
|
70
|
+
*/
|
|
71
|
+
nullEmbeddingFallback?: 'token-overlap' | 'skip';
|
|
72
|
+
}
|
|
73
|
+
export declare class EmbeddingUserMemory implements UserMemory {
|
|
74
|
+
private readonly inner;
|
|
75
|
+
private readonly model;
|
|
76
|
+
private readonly threshold;
|
|
77
|
+
private readonly fallback;
|
|
78
|
+
constructor(opts: EmbeddingUserMemoryOptions);
|
|
79
|
+
remember(userId: string, fact: string, opts?: {
|
|
80
|
+
tags?: string[];
|
|
81
|
+
score?: number;
|
|
82
|
+
}): Promise<MemoryEntry>;
|
|
83
|
+
recall(userId: string, query: string, opts?: {
|
|
84
|
+
limit?: number;
|
|
85
|
+
tags?: string[];
|
|
86
|
+
}): Promise<MemoryEntry[]>;
|
|
87
|
+
forget(userId: string, factId: string): Promise<void>;
|
|
88
|
+
list(userId: string, opts?: {
|
|
89
|
+
tags?: string[];
|
|
90
|
+
limit?: number;
|
|
91
|
+
}): Promise<MemoryEntry[]>;
|
|
92
|
+
forgetAll(userId: string): Promise<void>;
|
|
93
|
+
/**
|
|
94
|
+
* Single-string embedding via the {@link AI} facade. Returns the
|
|
95
|
+
* first (and only) embedding vector. Throws on provider/network
|
|
96
|
+
* failure; callers route through try/catch and degrade.
|
|
97
|
+
*/
|
|
98
|
+
private embed;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Pack a `number[]` into a Float32 byte buffer. 4 bytes per dim;
|
|
102
|
+
* a 1536-dim OpenAI embedding compresses to 6144 bytes.
|
|
103
|
+
*
|
|
104
|
+
* Uses `ArrayBuffer` + `Float32Array` so the output is a portable
|
|
105
|
+
* `Uint8Array` (works in Node, browser, RN). Prisma's `Bytes`
|
|
106
|
+
* column accepts both `Uint8Array` and `Buffer`.
|
|
107
|
+
*/
|
|
108
|
+
export declare function serializeVector(v: number[]): Uint8Array;
|
|
109
|
+
/**
|
|
110
|
+
* Reverse of {@link serializeVector}. Reads the underlying byte
|
|
111
|
+
* buffer as Float32 and returns a fresh `number[]` so callers can
|
|
112
|
+
* mutate without affecting the source row.
|
|
113
|
+
*/
|
|
114
|
+
export declare function deserializeVector(bytes: Uint8Array): number[];
|
|
115
|
+
/**
|
|
116
|
+
* Cosine similarity in `[-1, 1]`. Returns `0` when either vector
|
|
117
|
+
* has zero magnitude, or when lengths don't match (defensive — should
|
|
118
|
+
* never happen if remember/recall use the same embedding model).
|
|
119
|
+
*/
|
|
120
|
+
export declare function cosineSimilarity(a: number[], b: number[]): number;
|
|
2
121
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/memory-embedding/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/memory-embedding/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAGH,OAAO,EAAE,aAAa,EAAoB,MAAM,wBAAwB,CAAA;AACxE,OAAO,KAAK,EACV,WAAW,EACX,UAAU,EACX,MAAM,kBAAkB,CAAA;AAEzB,MAAM,WAAW,0BAA0B;IACzC;;;;;OAKG;IACH,KAAK,EAAE,aAAa,CAAA;IACpB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;;;;;OASG;IACH,qBAAqB,CAAC,EAAE,eAAe,GAAG,MAAM,CAAA;CACjD;AAED,qBAAa,mBAAoB,YAAW,UAAU;IACpD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkB;IACxC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAuB;IAC7C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAQ;IAClC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA0B;gBAEvC,IAAI,EAAE,0BAA0B;IAOtC,QAAQ,CACZ,MAAM,EAAE,MAAM,EACd,IAAI,EAAI,MAAM,EACd,IAAI,CAAC,EAAG;QAAE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAC1C,OAAO,CAAC,WAAW,CAAC;IAkBjB,MAAM,CACV,MAAM,EAAE,MAAM,EACd,KAAK,EAAG,MAAM,EACd,IAAI,CAAC,EAAG;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GAC1C,OAAO,CAAC,WAAW,EAAE,CAAC;IA4CnB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMrD,IAAI,CACR,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAG;QAAE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAC1C,OAAO,CAAC,WAAW,EAAE,CAAC;IAInB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK9C;;;;OAIG;YACW,KAAK;CAMpB;AAID;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,UAAU,CAKvD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,EAAE,CAK7D;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CAcjE"}
|