mnemocyte 0.1.2 → 0.1.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 +162 -102
- package/dist/embedders/openai.d.mts +31 -0
- package/dist/embedders/openai.d.mts.map +1 -0
- package/dist/embedders/openai.mjs +78 -0
- package/dist/embedders/openai.mjs.map +1 -0
- package/dist/errors-BG3-mNur.mjs +53 -0
- package/dist/errors-BG3-mNur.mjs.map +1 -0
- package/dist/index.d.mts +2 -843
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +149 -109
- package/dist/index.mjs.map +1 -1
- package/dist/types-QK_43oKJ.d.mts +845 -0
- package/dist/types-QK_43oKJ.d.mts.map +1 -0
- package/migrations/0000_initial.sql +33 -44
- package/migrations/meta/0000_snapshot.json +284 -0
- package/migrations/meta/_journal.json +13 -0
- package/package.json +25 -17
package/README.md
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
# Mnemocyte
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Infrastructure-native memory for TypeScript AI applications.
|
|
4
4
|
|
|
5
|
-
Mnemocyte stores
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
a separate vector database.
|
|
5
|
+
Mnemocyte stores durable memories for users, agents, sessions, and other
|
|
6
|
+
entities. It gives your app hybrid recall, prompt-ready context, pruning, audit
|
|
7
|
+
logs, and duplicate-consolidation tools on top of your existing infrastructure.
|
|
9
8
|
|
|
10
9
|
> Mnemocyte is early software. APIs may change before v1.0.
|
|
11
10
|
|
|
12
11
|
## Features
|
|
13
12
|
|
|
14
|
-
- In-memory backend for tests
|
|
13
|
+
- In-memory backend for tests and demos.
|
|
15
14
|
- Postgres + pgvector backend for persistent storage.
|
|
16
|
-
-
|
|
17
|
-
|
|
15
|
+
- User-supplied `Embedder` interface.
|
|
16
|
+
- Hybrid recall with vector similarity, lexical matching, recency, confidence,
|
|
17
|
+
access count, and importance.
|
|
18
18
|
- Prompt-ready context output in Markdown, plain text, or XML.
|
|
19
19
|
- Provider timeouts, retries, and `AbortSignal` cancellation.
|
|
20
20
|
- Pruning, duplicate detection, audit logs, and experimental consolidation.
|
|
@@ -55,92 +55,145 @@ const memories = await client.recall({
|
|
|
55
55
|
explain: true,
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
+
const context = await client.buildContext({
|
|
59
|
+
entityId: "user_123",
|
|
60
|
+
query: "How should I respond?",
|
|
61
|
+
format: "markdown",
|
|
62
|
+
maxTokens: 500,
|
|
63
|
+
});
|
|
64
|
+
|
|
58
65
|
await client.close();
|
|
59
66
|
```
|
|
60
67
|
|
|
61
68
|
When `databaseUrl` is omitted, Mnemocyte uses the in-memory backend.
|
|
62
69
|
|
|
63
|
-
##
|
|
70
|
+
## Embedder
|
|
64
71
|
|
|
65
|
-
|
|
66
|
-
pass `databaseUrl`:
|
|
72
|
+
Mnemocyte accepts any embedder that returns one vector per input text.
|
|
67
73
|
|
|
68
74
|
```ts
|
|
69
75
|
const client = createMnemocyte({
|
|
70
|
-
|
|
71
|
-
|
|
76
|
+
embedder: {
|
|
77
|
+
model: "text-embedding-3-small",
|
|
78
|
+
dimensions: 1536,
|
|
79
|
+
async embed(texts, options) {
|
|
80
|
+
return embedWithYourProvider(texts, {
|
|
81
|
+
signal: options?.signal,
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
},
|
|
72
85
|
});
|
|
73
86
|
```
|
|
74
87
|
|
|
75
|
-
|
|
88
|
+
You can also use the optional OpenAI helper:
|
|
76
89
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
`MnemocyteError` with code `"CONFIG"` before opening the connection pool. The
|
|
80
|
-
in-memory backend does not enforce this constraint.
|
|
90
|
+
```ts
|
|
91
|
+
import { openaiEmbedder } from "mnemocyte/embedders/openai";
|
|
81
92
|
|
|
82
|
-
|
|
93
|
+
const embedder = openaiEmbedder({
|
|
94
|
+
model: "text-embedding-3-small",
|
|
95
|
+
});
|
|
96
|
+
```
|
|
83
97
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
98
|
+
`openaiEmbedder()` reads `OPENAI_API_KEY` by default. Pass `apiKey`
|
|
99
|
+
explicitly when you use a different key source. The helper uses `fetch`
|
|
100
|
+
directly and does not depend on the OpenAI SDK; plain `import "mnemocyte"` also
|
|
101
|
+
stays provider-free.
|
|
102
|
+
|
|
103
|
+
## Postgres
|
|
104
|
+
|
|
105
|
+
For persistent storage, apply the bundled migration to a Postgres database with
|
|
106
|
+
pgvector enabled, then pass `databaseUrl`.
|
|
87
107
|
|
|
88
108
|
```ts
|
|
89
109
|
const client = createMnemocyte({
|
|
110
|
+
databaseUrl: process.env.DATABASE_URL!,
|
|
90
111
|
embedder,
|
|
91
|
-
provider: {
|
|
92
|
-
timeoutMs: 5_000,
|
|
93
|
-
retries: 2,
|
|
94
|
-
baseDelayMs: 200,
|
|
95
|
-
maxDelayMs: 2_000,
|
|
96
|
-
},
|
|
97
112
|
});
|
|
113
|
+
```
|
|
98
114
|
|
|
99
|
-
|
|
100
|
-
setTimeout(() => controller.abort(), 1_000);
|
|
115
|
+
The package includes:
|
|
101
116
|
|
|
117
|
+
```txt
|
|
118
|
+
migrations/0000_initial.sql
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The current Postgres schema uses `embedding vector(1536)`, so the Postgres
|
|
122
|
+
backend currently requires a 1536-dimensional embedder.
|
|
123
|
+
|
|
124
|
+
If dimensions do not match, `createMnemocyte` throws a `MnemocyteError` with
|
|
125
|
+
code `"CONFIG"` before opening the connection pool.
|
|
126
|
+
|
|
127
|
+
The migration creates the bundled HNSW pgvector index
|
|
128
|
+
`mnemocyte_memories_embedding_hnsw_idx` for cosine search. HNSW is approximate:
|
|
129
|
+
it is fast for nearest-neighbor recall, but recall quality, index build memory,
|
|
130
|
+
and write overhead should be benchmarked against your data volume and write
|
|
131
|
+
rate. Postgres still applies ordinary filters such as `entity_id`, `type`, and
|
|
132
|
+
tags around vector search, so highly selective filters may need query tuning or
|
|
133
|
+
a workload-specific index strategy.
|
|
134
|
+
|
|
135
|
+
Large production tables should benchmark alternate indexes or custom
|
|
136
|
+
migrations with representative data before changing the default. IVFFlat can be
|
|
137
|
+
a good fit for some large, steady-state tables, but it needs tuning after the
|
|
138
|
+
table has representative vectors. The bundled migration intentionally does not
|
|
139
|
+
add full-text or tag-specific GIN indexes yet; add and benchmark expression
|
|
140
|
+
indexes such as `to_tsvector('english', content)` or tag-oriented indexes only
|
|
141
|
+
when your workload needs them.
|
|
142
|
+
|
|
143
|
+
Configurable embedding dimensions are planned for `0.2.0`.
|
|
144
|
+
|
|
145
|
+
## API
|
|
146
|
+
|
|
147
|
+
### `remember`
|
|
148
|
+
|
|
149
|
+
```ts
|
|
102
150
|
await client.remember({
|
|
103
151
|
entityId: "user_123",
|
|
104
|
-
content: "
|
|
105
|
-
|
|
152
|
+
content: "Likes concise answers.",
|
|
153
|
+
type: "preference",
|
|
154
|
+
importance: "high",
|
|
155
|
+
tags: ["communication"],
|
|
106
156
|
});
|
|
107
157
|
```
|
|
108
158
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
- `"TIMEOUT"`: a provider attempt exceeded `timeoutMs`.
|
|
112
|
-
- `"ABORTED"`: the operation was cancelled via `signal` and is not retried.
|
|
113
|
-
- `"EMBEDDING"`: the embedder failed after retries were exhausted.
|
|
159
|
+
### `recall`
|
|
114
160
|
|
|
115
|
-
|
|
161
|
+
```ts
|
|
162
|
+
const memories = await client.recall({
|
|
163
|
+
entityId: "user_123",
|
|
164
|
+
query: "How should I respond to this user?",
|
|
165
|
+
limit: 5,
|
|
166
|
+
explain: true,
|
|
167
|
+
});
|
|
168
|
+
```
|
|
116
169
|
|
|
117
|
-
`
|
|
118
|
-
`prune({})` is rejected to avoid accidental full deletion.
|
|
170
|
+
### `buildContext`
|
|
119
171
|
|
|
120
172
|
```ts
|
|
121
|
-
await client.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
173
|
+
const context = await client.buildContext({
|
|
174
|
+
entityId: "user_123",
|
|
175
|
+
query: "User communication preferences",
|
|
176
|
+
format: "markdown",
|
|
177
|
+
maxTokens: 500,
|
|
126
178
|
});
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### `prune`
|
|
127
182
|
|
|
183
|
+
```ts
|
|
128
184
|
const preview = await client.prune({
|
|
129
185
|
entityId: "user_123",
|
|
130
186
|
superseded: true,
|
|
131
187
|
dryRun: true,
|
|
132
188
|
});
|
|
133
|
-
console.log(preview.matchedCount);
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
Available selectors: `entityId`, `expired`, `superseded`, `createdBefore`,
|
|
137
|
-
`notAccessedSince`, `types`, `tags`, `maxImportance`. Selectors are combined
|
|
138
|
-
with AND semantics.
|
|
139
189
|
|
|
140
|
-
|
|
190
|
+
await client.prune({
|
|
191
|
+
entityId: "user_123",
|
|
192
|
+
expired: true,
|
|
193
|
+
});
|
|
194
|
+
```
|
|
141
195
|
|
|
142
|
-
`findDuplicates`
|
|
143
|
-
near-identical memories for one entity using pairwise cosine similarity.
|
|
196
|
+
### `findDuplicates`
|
|
144
197
|
|
|
145
198
|
```ts
|
|
146
199
|
const pairs = await client.findDuplicates({
|
|
@@ -148,20 +201,9 @@ const pairs = await client.findDuplicates({
|
|
|
148
201
|
threshold: 0.95,
|
|
149
202
|
limit: 50,
|
|
150
203
|
});
|
|
151
|
-
|
|
152
|
-
for (const { a, b, similarity } of pairs) {
|
|
153
|
-
console.log(similarity.toFixed(3), a.content, "<>", b.content);
|
|
154
|
-
}
|
|
155
204
|
```
|
|
156
205
|
|
|
157
|
-
|
|
158
|
-
`types` and `tags` must match on both memories in each pair. Superseded and
|
|
159
|
-
expired memories are excluded by default.
|
|
160
|
-
|
|
161
|
-
## Audit Log
|
|
162
|
-
|
|
163
|
-
When `audit.enabled` is `true`, Mnemocyte records an entry for state-changing
|
|
164
|
-
operations. Read entries with `client.listAuditLog()`.
|
|
206
|
+
### `listAuditLog`
|
|
165
207
|
|
|
166
208
|
```ts
|
|
167
209
|
const client = createMnemocyte({
|
|
@@ -169,53 +211,61 @@ const client = createMnemocyte({
|
|
|
169
211
|
audit: { enabled: true },
|
|
170
212
|
});
|
|
171
213
|
|
|
172
|
-
await client.
|
|
173
|
-
await client.prune({ entityId: "user_123", expired: true });
|
|
174
|
-
|
|
175
|
-
const log = await client.listAuditLog({
|
|
214
|
+
const events = await client.listAuditLog({
|
|
176
215
|
entityId: "user_123",
|
|
177
216
|
limit: 100,
|
|
178
217
|
});
|
|
179
|
-
|
|
180
|
-
for (const event of log) {
|
|
181
|
-
console.log(event.timestamp, event.description, event.metadata);
|
|
182
|
-
}
|
|
183
218
|
```
|
|
184
219
|
|
|
185
|
-
|
|
220
|
+
### `experimental.consolidate`
|
|
186
221
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
222
|
+
```ts
|
|
223
|
+
await client.experimental.consolidate({
|
|
224
|
+
entityId: "user_123",
|
|
225
|
+
survivorId: "mem_survivor",
|
|
226
|
+
supersededIds: ["mem_duplicate"],
|
|
227
|
+
});
|
|
228
|
+
```
|
|
192
229
|
|
|
193
|
-
|
|
194
|
-
audit by leaving `audit.enabled` unset.
|
|
230
|
+
The `experimental` namespace may change before v1.0.
|
|
195
231
|
|
|
196
|
-
##
|
|
232
|
+
## Provider Resilience
|
|
197
233
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
234
|
+
```ts
|
|
235
|
+
const client = createMnemocyte({
|
|
236
|
+
embedder,
|
|
237
|
+
provider: {
|
|
238
|
+
timeoutMs: 5_000,
|
|
239
|
+
retries: 2,
|
|
240
|
+
baseDelayMs: 200,
|
|
241
|
+
maxDelayMs: 2_000,
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Pass `signal` to cancel an operation:
|
|
201
247
|
|
|
202
248
|
```ts
|
|
203
|
-
const
|
|
249
|
+
const controller = new AbortController();
|
|
250
|
+
|
|
251
|
+
await client.remember({
|
|
204
252
|
entityId: "user_123",
|
|
205
|
-
|
|
253
|
+
content: "Prefers short answers.",
|
|
254
|
+
signal: controller.signal,
|
|
206
255
|
});
|
|
207
|
-
|
|
208
|
-
for (const { a, b } of pairs) {
|
|
209
|
-
await client.experimental.consolidate({
|
|
210
|
-
entityId: "user_123",
|
|
211
|
-
survivorId: a.id,
|
|
212
|
-
supersededIds: [b.id],
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
256
|
```
|
|
216
257
|
|
|
217
|
-
|
|
218
|
-
|
|
258
|
+
Common `MnemocyteError` codes:
|
|
259
|
+
|
|
260
|
+
```txt
|
|
261
|
+
TIMEOUT
|
|
262
|
+
ABORTED
|
|
263
|
+
EMBEDDING
|
|
264
|
+
VALIDATION
|
|
265
|
+
CONFIG
|
|
266
|
+
DB
|
|
267
|
+
NOT_FOUND
|
|
268
|
+
```
|
|
219
269
|
|
|
220
270
|
## Retrieval Tuning
|
|
221
271
|
|
|
@@ -238,19 +288,29 @@ const client = createMnemocyte({
|
|
|
238
288
|
});
|
|
239
289
|
```
|
|
240
290
|
|
|
291
|
+
## Roadmap
|
|
292
|
+
|
|
293
|
+
Planned larger milestones:
|
|
294
|
+
|
|
295
|
+
- configurable embedding dimensions with `mnemocyte_meta`
|
|
296
|
+
- `MemoryStore` abstraction
|
|
297
|
+
- `drizzleStore(db)` for caller-owned Drizzle clients
|
|
298
|
+
- `@mnemocyte/mcp`
|
|
299
|
+
|
|
300
|
+
See [ROADMAP.md](./ROADMAP.md).
|
|
301
|
+
|
|
241
302
|
## Development
|
|
242
303
|
|
|
243
304
|
```bash
|
|
244
305
|
pnpm checktypes
|
|
245
306
|
pnpm lint
|
|
246
|
-
pnpm
|
|
307
|
+
pnpm test
|
|
247
308
|
pnpm run test:integration
|
|
248
309
|
pnpm run pack:check
|
|
249
310
|
```
|
|
250
311
|
|
|
251
|
-
`test:integration` skips
|
|
312
|
+
`test:integration` skips when `DATABASE_URL` is not set.
|
|
252
313
|
|
|
253
314
|
## Architecture
|
|
254
315
|
|
|
255
|
-
See [ARCHITECTURE.md](./ARCHITECTURE.md)
|
|
256
|
-
[ROADMAP.md](./ROADMAP.md) for planned work.
|
|
316
|
+
See [ARCHITECTURE.md](./ARCHITECTURE.md).
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { c as Embedder } from "../types-QK_43oKJ.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/embedders/openai.d.ts
|
|
4
|
+
interface OpenAIEmbedderOptions {
|
|
5
|
+
/**
|
|
6
|
+
* OpenAI API key. Defaults to `process.env.OPENAI_API_KEY`.
|
|
7
|
+
*/
|
|
8
|
+
readonly apiKey?: string;
|
|
9
|
+
/**
|
|
10
|
+
* OpenAI embedding model name.
|
|
11
|
+
*/
|
|
12
|
+
readonly model: string;
|
|
13
|
+
/**
|
|
14
|
+
* Output dimensions. Required for unknown/future model names and accepted
|
|
15
|
+
* only for OpenAI models that support configurable dimensions.
|
|
16
|
+
*/
|
|
17
|
+
readonly dimensions?: number;
|
|
18
|
+
/**
|
|
19
|
+
* Override the OpenAI API base URL. Intended for tests and compatible
|
|
20
|
+
* deployments; defaults to `https://api.openai.com/v1`.
|
|
21
|
+
*/
|
|
22
|
+
readonly baseUrl?: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Create an OpenAI-backed {@link Embedder}. This adapter uses `fetch`
|
|
26
|
+
* directly so provider SDKs never become part of Mnemocyte's dependency graph.
|
|
27
|
+
*/
|
|
28
|
+
declare function openaiEmbedder(options: OpenAIEmbedderOptions): Embedder;
|
|
29
|
+
//#endregion
|
|
30
|
+
export { OpenAIEmbedderOptions, openaiEmbedder };
|
|
31
|
+
//# sourceMappingURL=openai.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai.d.mts","names":[],"sources":["../../src/embedders/openai.ts"],"mappings":";;;UAciB,qBAAA;;AAAjB;;WAIU,MAAA;EAJ4B;;;EAAA,SAQ5B,KAAA;EAUA;;AAAO;AA4EjB;EA5EU,SALA,UAAA;;;;;WAKA,OAAA;AAAA;AA4E8D;;;;AAAA,iBAAxD,cAAA,CAAe,OAAA,EAAS,qBAAA,GAAwB,QAAQ"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { t as MnemocyteError } from "../errors-BG3-mNur.mjs";
|
|
2
|
+
//#region src/embedders/openai.ts
|
|
3
|
+
const KNOWN_MODEL_DIMENSIONS = {
|
|
4
|
+
"text-embedding-3-small": 1536,
|
|
5
|
+
"text-embedding-3-large": 3072,
|
|
6
|
+
"text-embedding-ada-002": 1536
|
|
7
|
+
};
|
|
8
|
+
const CONFIGURABLE_DIMENSION_MODELS = new Set(["text-embedding-3-small", "text-embedding-3-large"]);
|
|
9
|
+
function assertDimensions(dimensions) {
|
|
10
|
+
if (!Number.isInteger(dimensions) || dimensions < 1) throw new MnemocyteError("openaiEmbedder dimensions must be a positive integer.", "CONFIG");
|
|
11
|
+
}
|
|
12
|
+
function resolveApiKey(apiKey) {
|
|
13
|
+
const resolved = apiKey ?? process.env.OPENAI_API_KEY;
|
|
14
|
+
if (!resolved || resolved.trim().length === 0) throw new MnemocyteError("openaiEmbedder requires an API key. Pass apiKey or set OPENAI_API_KEY.", "CONFIG");
|
|
15
|
+
return resolved;
|
|
16
|
+
}
|
|
17
|
+
function resolveDimensions(options) {
|
|
18
|
+
const defaultDimensions = KNOWN_MODEL_DIMENSIONS[options.model];
|
|
19
|
+
if (defaultDimensions === void 0) {
|
|
20
|
+
if (options.dimensions === void 0) throw new MnemocyteError(`openaiEmbedder requires dimensions for unknown model "${options.model}".`, "CONFIG");
|
|
21
|
+
assertDimensions(options.dimensions);
|
|
22
|
+
return {
|
|
23
|
+
embedderDimensions: options.dimensions,
|
|
24
|
+
requestDimensions: options.dimensions
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
if (options.dimensions !== void 0 && !CONFIGURABLE_DIMENSION_MODELS.has(options.model)) throw new MnemocyteError(`openaiEmbedder does not support a dimensions override for "${options.model}".`, "CONFIG");
|
|
28
|
+
if (options.dimensions !== void 0) {
|
|
29
|
+
assertDimensions(options.dimensions);
|
|
30
|
+
return {
|
|
31
|
+
embedderDimensions: options.dimensions,
|
|
32
|
+
requestDimensions: options.dimensions
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
return { embedderDimensions: defaultDimensions };
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Create an OpenAI-backed {@link Embedder}. This adapter uses `fetch`
|
|
39
|
+
* directly so provider SDKs never become part of Mnemocyte's dependency graph.
|
|
40
|
+
*/
|
|
41
|
+
function openaiEmbedder(options) {
|
|
42
|
+
const apiKey = resolveApiKey(options.apiKey);
|
|
43
|
+
const { embedderDimensions, requestDimensions } = resolveDimensions(options);
|
|
44
|
+
const baseUrl = options.baseUrl ?? "https://api.openai.com/v1";
|
|
45
|
+
return {
|
|
46
|
+
model: options.model,
|
|
47
|
+
dimensions: embedderDimensions,
|
|
48
|
+
async embed(texts, embedOptions) {
|
|
49
|
+
const response = await fetch(`${baseUrl}/embeddings`, {
|
|
50
|
+
method: "POST",
|
|
51
|
+
headers: {
|
|
52
|
+
authorization: `Bearer ${apiKey}`,
|
|
53
|
+
"content-type": "application/json"
|
|
54
|
+
},
|
|
55
|
+
body: JSON.stringify({
|
|
56
|
+
model: options.model,
|
|
57
|
+
input: [...texts],
|
|
58
|
+
encoding_format: "float",
|
|
59
|
+
...requestDimensions === void 0 ? {} : { dimensions: requestDimensions }
|
|
60
|
+
}),
|
|
61
|
+
...embedOptions?.signal === void 0 ? {} : { signal: embedOptions.signal }
|
|
62
|
+
});
|
|
63
|
+
if (!response.ok) throw Object.assign(/* @__PURE__ */ new Error(`OpenAI embeddings request failed with status ${response.status}.`), { status: response.status });
|
|
64
|
+
const body = await response.json();
|
|
65
|
+
const embeddings = Array.from({ length: texts.length });
|
|
66
|
+
for (const item of body.data) {
|
|
67
|
+
if (!Number.isInteger(item.index) || item.index < 0 || item.index >= texts.length) throw new MnemocyteError("OpenAI returned an embedding with an invalid index.", "EMBEDDING");
|
|
68
|
+
embeddings[item.index] = item.embedding;
|
|
69
|
+
}
|
|
70
|
+
if (embeddings.some((embedding) => embedding === void 0)) throw new MnemocyteError("OpenAI returned fewer embeddings than requested.", "EMBEDDING");
|
|
71
|
+
return embeddings;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
//#endregion
|
|
76
|
+
export { openaiEmbedder };
|
|
77
|
+
|
|
78
|
+
//# sourceMappingURL=openai.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai.mjs","names":[],"sources":["../../src/embedders/openai.ts"],"sourcesContent":["import { MnemocyteError } from \"../errors.js\";\nimport type { Embedder } from \"../types.js\";\n\nconst KNOWN_MODEL_DIMENSIONS: Record<string, number> = {\n\t\"text-embedding-3-small\": 1536,\n\t\"text-embedding-3-large\": 3072,\n\t\"text-embedding-ada-002\": 1536,\n};\n\nconst CONFIGURABLE_DIMENSION_MODELS = new Set([\n\t\"text-embedding-3-small\",\n\t\"text-embedding-3-large\",\n]);\n\nexport interface OpenAIEmbedderOptions {\n\t/**\n\t * OpenAI API key. Defaults to `process.env.OPENAI_API_KEY`.\n\t */\n\treadonly apiKey?: string;\n\t/**\n\t * OpenAI embedding model name.\n\t */\n\treadonly model: string;\n\t/**\n\t * Output dimensions. Required for unknown/future model names and accepted\n\t * only for OpenAI models that support configurable dimensions.\n\t */\n\treadonly dimensions?: number;\n\t/**\n\t * Override the OpenAI API base URL. Intended for tests and compatible\n\t * deployments; defaults to `https://api.openai.com/v1`.\n\t */\n\treadonly baseUrl?: string;\n}\n\ninterface OpenAIEmbeddingItem {\n\treadonly embedding: number[];\n\treadonly index: number;\n}\n\ninterface OpenAIEmbeddingResponse {\n\treadonly data: OpenAIEmbeddingItem[];\n}\n\nfunction assertDimensions(dimensions: number): void {\n\tif (!Number.isInteger(dimensions) || dimensions < 1) {\n\t\tthrow new MnemocyteError(\n\t\t\t\"openaiEmbedder dimensions must be a positive integer.\",\n\t\t\t\"CONFIG\",\n\t\t);\n\t}\n}\n\nfunction resolveApiKey(apiKey: string | undefined): string {\n\tconst resolved = apiKey ?? process.env.OPENAI_API_KEY;\n\tif (!resolved || resolved.trim().length === 0) {\n\t\tthrow new MnemocyteError(\n\t\t\t\"openaiEmbedder requires an API key. Pass apiKey or set OPENAI_API_KEY.\",\n\t\t\t\"CONFIG\",\n\t\t);\n\t}\n\treturn resolved;\n}\n\nfunction resolveDimensions(options: OpenAIEmbedderOptions): {\n\tembedderDimensions: number;\n\trequestDimensions?: number;\n} {\n\tconst defaultDimensions = KNOWN_MODEL_DIMENSIONS[options.model];\n\tif (defaultDimensions === undefined) {\n\t\tif (options.dimensions === undefined) {\n\t\t\tthrow new MnemocyteError(\n\t\t\t\t`openaiEmbedder requires dimensions for unknown model \"${options.model}\".`,\n\t\t\t\t\"CONFIG\",\n\t\t\t);\n\t\t}\n\t\tassertDimensions(options.dimensions);\n\t\treturn {\n\t\t\tembedderDimensions: options.dimensions,\n\t\t\trequestDimensions: options.dimensions,\n\t\t};\n\t}\n\n\tif (\n\t\toptions.dimensions !== undefined &&\n\t\t!CONFIGURABLE_DIMENSION_MODELS.has(options.model)\n\t) {\n\t\tthrow new MnemocyteError(\n\t\t\t`openaiEmbedder does not support a dimensions override for \"${options.model}\".`,\n\t\t\t\"CONFIG\",\n\t\t);\n\t}\n\n\tif (options.dimensions !== undefined) {\n\t\tassertDimensions(options.dimensions);\n\t\treturn {\n\t\t\tembedderDimensions: options.dimensions,\n\t\t\trequestDimensions: options.dimensions,\n\t\t};\n\t}\n\n\treturn { embedderDimensions: defaultDimensions };\n}\n\n/**\n * Create an OpenAI-backed {@link Embedder}. This adapter uses `fetch`\n * directly so provider SDKs never become part of Mnemocyte's dependency graph.\n */\nexport function openaiEmbedder(options: OpenAIEmbedderOptions): Embedder {\n\tconst apiKey = resolveApiKey(options.apiKey);\n\tconst { embedderDimensions, requestDimensions } = resolveDimensions(options);\n\tconst baseUrl = options.baseUrl ?? \"https://api.openai.com/v1\";\n\n\treturn {\n\t\tmodel: options.model,\n\t\tdimensions: embedderDimensions,\n\t\tasync embed(texts, embedOptions) {\n\t\t\tconst response = await fetch(`${baseUrl}/embeddings`, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tauthorization: `Bearer ${apiKey}`,\n\t\t\t\t\t\"content-type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tmodel: options.model,\n\t\t\t\t\tinput: [...texts],\n\t\t\t\t\tencoding_format: \"float\",\n\t\t\t\t\t...(requestDimensions === undefined\n\t\t\t\t\t\t? {}\n\t\t\t\t\t\t: { dimensions: requestDimensions }),\n\t\t\t\t}),\n\t\t\t\t...(embedOptions?.signal === undefined\n\t\t\t\t\t? {}\n\t\t\t\t\t: { signal: embedOptions.signal }),\n\t\t\t});\n\n\t\t\tif (!response.ok) {\n\t\t\t\tthrow Object.assign(\n\t\t\t\t\tnew Error(\n\t\t\t\t\t\t`OpenAI embeddings request failed with status ${response.status}.`,\n\t\t\t\t\t),\n\t\t\t\t\t{ status: response.status },\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst body = (await response.json()) as OpenAIEmbeddingResponse;\n\n\t\t\tconst embeddings: Array<number[] | undefined> = Array.from({\n\t\t\t\tlength: texts.length,\n\t\t\t});\n\t\t\tfor (const item of body.data) {\n\t\t\t\tif (\n\t\t\t\t\t!Number.isInteger(item.index) ||\n\t\t\t\t\titem.index < 0 ||\n\t\t\t\t\titem.index >= texts.length\n\t\t\t\t) {\n\t\t\t\t\tthrow new MnemocyteError(\n\t\t\t\t\t\t\"OpenAI returned an embedding with an invalid index.\",\n\t\t\t\t\t\t\"EMBEDDING\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tembeddings[item.index] = item.embedding;\n\t\t\t}\n\t\t\tif (embeddings.some((embedding) => embedding === undefined)) {\n\t\t\t\tthrow new MnemocyteError(\n\t\t\t\t\t\"OpenAI returned fewer embeddings than requested.\",\n\t\t\t\t\t\"EMBEDDING\",\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn embeddings as number[][];\n\t\t},\n\t};\n}\n"],"mappings":";;AAGA,MAAM,yBAAiD;CACtD,0BAA0B;CAC1B,0BAA0B;CAC1B,0BAA0B;AAC3B;AAEA,MAAM,gCAAgC,IAAI,IAAI,CAC7C,0BACA,wBACD,CAAC;AAgCD,SAAS,iBAAiB,YAA0B;CACnD,IAAI,CAAC,OAAO,UAAU,UAAU,KAAK,aAAa,GACjD,MAAM,IAAI,eACT,yDACA,QACD;AAEF;AAEA,SAAS,cAAc,QAAoC;CAC1D,MAAM,WAAW,UAAU,QAAQ,IAAI;CACvC,IAAI,CAAC,YAAY,SAAS,KAAK,EAAE,WAAW,GAC3C,MAAM,IAAI,eACT,0EACA,QACD;CAED,OAAO;AACR;AAEA,SAAS,kBAAkB,SAGzB;CACD,MAAM,oBAAoB,uBAAuB,QAAQ;CACzD,IAAI,sBAAsB,KAAA,GAAW;EACpC,IAAI,QAAQ,eAAe,KAAA,GAC1B,MAAM,IAAI,eACT,yDAAyD,QAAQ,MAAM,KACvE,QACD;EAED,iBAAiB,QAAQ,UAAU;EACnC,OAAO;GACN,oBAAoB,QAAQ;GAC5B,mBAAmB,QAAQ;EAC5B;CACD;CAEA,IACC,QAAQ,eAAe,KAAA,KACvB,CAAC,8BAA8B,IAAI,QAAQ,KAAK,GAEhD,MAAM,IAAI,eACT,8DAA8D,QAAQ,MAAM,KAC5E,QACD;CAGD,IAAI,QAAQ,eAAe,KAAA,GAAW;EACrC,iBAAiB,QAAQ,UAAU;EACnC,OAAO;GACN,oBAAoB,QAAQ;GAC5B,mBAAmB,QAAQ;EAC5B;CACD;CAEA,OAAO,EAAE,oBAAoB,kBAAkB;AAChD;;;;;AAMA,SAAgB,eAAe,SAA0C;CACxE,MAAM,SAAS,cAAc,QAAQ,MAAM;CAC3C,MAAM,EAAE,oBAAoB,sBAAsB,kBAAkB,OAAO;CAC3E,MAAM,UAAU,QAAQ,WAAW;CAEnC,OAAO;EACN,OAAO,QAAQ;EACf,YAAY;EACZ,MAAM,MAAM,OAAO,cAAc;GAChC,MAAM,WAAW,MAAM,MAAM,GAAG,QAAQ,cAAc;IACrD,QAAQ;IACR,SAAS;KACR,eAAe,UAAU;KACzB,gBAAgB;IACjB;IACA,MAAM,KAAK,UAAU;KACpB,OAAO,QAAQ;KACf,OAAO,CAAC,GAAG,KAAK;KAChB,iBAAiB;KACjB,GAAI,sBAAsB,KAAA,IACvB,CAAC,IACD,EAAE,YAAY,kBAAkB;IACpC,CAAC;IACD,GAAI,cAAc,WAAW,KAAA,IAC1B,CAAC,IACD,EAAE,QAAQ,aAAa,OAAO;GAClC,CAAC;GAED,IAAI,CAAC,SAAS,IACb,MAAM,OAAO,uBACZ,IAAI,MACH,gDAAgD,SAAS,OAAO,EACjE,GACA,EAAE,QAAQ,SAAS,OAAO,CAC3B;GAGD,MAAM,OAAQ,MAAM,SAAS,KAAK;GAElC,MAAM,aAA0C,MAAM,KAAK,EAC1D,QAAQ,MAAM,OACf,CAAC;GACD,KAAK,MAAM,QAAQ,KAAK,MAAM;IAC7B,IACC,CAAC,OAAO,UAAU,KAAK,KAAK,KAC5B,KAAK,QAAQ,KACb,KAAK,SAAS,MAAM,QAEpB,MAAM,IAAI,eACT,uDACA,WACD;IAED,WAAW,KAAK,SAAS,KAAK;GAC/B;GACA,IAAI,WAAW,MAAM,cAAc,cAAc,KAAA,CAAS,GACzD,MAAM,IAAI,eACT,oDACA,WACD;GAED,OAAO;EACR;CACD;AACD"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
//#region src/errors.ts
|
|
2
|
+
/**
|
|
3
|
+
* Error type thrown by every public Mnemocyte API.
|
|
4
|
+
*
|
|
5
|
+
* Use the {@link MnemocyteError.code} property — not the message — to branch
|
|
6
|
+
* on failure modes, or call {@link isMnemocyteError} as a type guard when
|
|
7
|
+
* working with `unknown` errors.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* try {
|
|
12
|
+
* await client.recall({ entityId: "u1", query: "hi" });
|
|
13
|
+
* } catch (err) {
|
|
14
|
+
* if (isMnemocyteError(err) && err.code === "DB") {
|
|
15
|
+
* // handle database failure
|
|
16
|
+
* }
|
|
17
|
+
* throw err;
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
var MnemocyteError = class extends Error {
|
|
22
|
+
/** Stable, machine-readable failure code. See {@link MnemocyteErrorCode}. */
|
|
23
|
+
code;
|
|
24
|
+
/** Underlying cause, if any (e.g. an original driver or embedder error). */
|
|
25
|
+
cause;
|
|
26
|
+
/**
|
|
27
|
+
* @param message - Human-readable error message.
|
|
28
|
+
* @param code - Stable failure code; see {@link MnemocyteErrorCode}.
|
|
29
|
+
* @param cause - Optional underlying cause (driver error, network error, etc.).
|
|
30
|
+
*/
|
|
31
|
+
constructor(message, code, cause) {
|
|
32
|
+
super(message);
|
|
33
|
+
this.name = "MnemocyteError";
|
|
34
|
+
this.code = code;
|
|
35
|
+
this.cause = cause;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Type guard for {@link MnemocyteError}.
|
|
40
|
+
*
|
|
41
|
+
* Prefer this over `instanceof` when working across module boundaries or
|
|
42
|
+
* bundlers where multiple copies of the class may exist.
|
|
43
|
+
*
|
|
44
|
+
* @param error - Value to test (typically a caught `unknown`).
|
|
45
|
+
* @returns `true` if `error` is a {@link MnemocyteError}.
|
|
46
|
+
*/
|
|
47
|
+
function isMnemocyteError(error) {
|
|
48
|
+
return error instanceof MnemocyteError;
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
export { isMnemocyteError as n, MnemocyteError as t };
|
|
52
|
+
|
|
53
|
+
//# sourceMappingURL=errors-BG3-mNur.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors-BG3-mNur.mjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/**\n * Stable, machine-readable error code attached to every {@link MnemocyteError}.\n *\n * - `\"CONFIG\"` — invalid client configuration (e.g. missing or malformed embedder).\n * - `\"VALIDATION\"` — invalid argument passed to a client method.\n * - `\"DB\"` — Postgres or driver-level failure.\n * - `\"EMBEDDING\"` — embedder threw or returned an invalid vector.\n * - `\"NOT_FOUND\"` — referenced memory or entity does not exist.\n * - `\"MIGRATION\"` — schema/migration check failed against the target database.\n * - `\"TIMEOUT\"` — a provider call exceeded its configured timeout.\n * - `\"ABORTED\"` — the operation was cancelled via an `AbortSignal`.\n */\nexport type MnemocyteErrorCode =\n\t| \"CONFIG\"\n\t| \"VALIDATION\"\n\t| \"DB\"\n\t| \"EMBEDDING\"\n\t| \"NOT_FOUND\"\n\t| \"MIGRATION\"\n\t| \"TIMEOUT\"\n\t| \"ABORTED\";\n\n/**\n * Error type thrown by every public Mnemocyte API.\n *\n * Use the {@link MnemocyteError.code} property — not the message — to branch\n * on failure modes, or call {@link isMnemocyteError} as a type guard when\n * working with `unknown` errors.\n *\n * @example\n * ```ts\n * try {\n * await client.recall({ entityId: \"u1\", query: \"hi\" });\n * } catch (err) {\n * if (isMnemocyteError(err) && err.code === \"DB\") {\n * // handle database failure\n * }\n * throw err;\n * }\n * ```\n */\nexport class MnemocyteError extends Error {\n\t/** Stable, machine-readable failure code. See {@link MnemocyteErrorCode}. */\n\treadonly code: MnemocyteErrorCode;\n\t/** Underlying cause, if any (e.g. an original driver or embedder error). */\n\toverride readonly cause?: unknown;\n\n\t/**\n\t * @param message - Human-readable error message.\n\t * @param code - Stable failure code; see {@link MnemocyteErrorCode}.\n\t * @param cause - Optional underlying cause (driver error, network error, etc.).\n\t */\n\tconstructor(message: string, code: MnemocyteErrorCode, cause?: unknown) {\n\t\tsuper(message);\n\t\tthis.name = \"MnemocyteError\";\n\t\tthis.code = code;\n\t\tthis.cause = cause;\n\t}\n}\n\n/**\n * Type guard for {@link MnemocyteError}.\n *\n * Prefer this over `instanceof` when working across module boundaries or\n * bundlers where multiple copies of the class may exist.\n *\n * @param error - Value to test (typically a caught `unknown`).\n * @returns `true` if `error` is a {@link MnemocyteError}.\n */\nexport function isMnemocyteError(error: unknown): error is MnemocyteError {\n\treturn error instanceof MnemocyteError;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAyCA,IAAa,iBAAb,cAAoC,MAAM;;CAEzC;;CAEA;;;;;;CAOA,YAAY,SAAiB,MAA0B,OAAiB;EACvE,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,KAAK,OAAO;EACZ,KAAK,QAAQ;CACd;AACD;;;;;;;;;;AAWA,SAAgB,iBAAiB,OAAyC;CACzE,OAAO,iBAAiB;AACzB"}
|