@ponraaj/dimple-sdk 0.0.0-alpha.1 → 0.0.0-alpha.2
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 +334 -0
- package/dist/index.js +45 -17
- package/dist/internal/config/schema.d.ts +27 -0
- package/dist/internal/embedder/transformers.d.ts +1 -1
- package/package.json +11 -1
package/README.md
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
# @ponraaj/dimple-sdk
|
|
2
|
+
|
|
3
|
+
A local-first memory system for AI agents. Store text memories (and whole
|
|
4
|
+
codebases), retrieve them with hybrid semantic + lexical search, and let a
|
|
5
|
+
**self-organizing topic tree + artifact graph + concepts** keep everything
|
|
6
|
+
structured as it grows. Everything runs locally: ONNX embeddings on your
|
|
7
|
+
CPU, SQLite files on disk.
|
|
8
|
+
|
|
9
|
+
This is the programmatic SDK — the
|
|
10
|
+
[`@ponraaj/dimple`](https://www.npmjs.com/package/@ponraaj/dimple) CLI is a
|
|
11
|
+
thin facade over it.
|
|
12
|
+
|
|
13
|
+
> **Status**: alpha (`0.0.0-alpha.n`). The API is stabilizing but may change.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @ponraaj/dimple-sdk
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Runtime dependencies are the only things installed (Effect, libSQL,
|
|
22
|
+
Transformers.js, drizzle) — the workspace internals are bundled into the
|
|
23
|
+
package.
|
|
24
|
+
|
|
25
|
+
## Quickstart
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { makeDimple } from "@ponraaj/dimple-sdk";
|
|
29
|
+
|
|
30
|
+
const dimple = await makeDimple({ config: "./dimple.jsonc" });
|
|
31
|
+
|
|
32
|
+
// write a memory (embeds + topic-routes it)
|
|
33
|
+
const mem = await dimple.write({
|
|
34
|
+
logicalKey: "deploy-1",
|
|
35
|
+
content: "the deploy pipeline runs on every push",
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// hybrid semantic + FTS search
|
|
39
|
+
const results = await dimple.query("how does the pipeline deploy", { limit: 3 });
|
|
40
|
+
|
|
41
|
+
// index real code — files + extracted functions/classes/methods become memories
|
|
42
|
+
const indexed = await dimple.index({ paths: ["./src"] });
|
|
43
|
+
|
|
44
|
+
await dimple.dispose(); // stop the worker, close DBs (idempotent)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## The problem & the approach
|
|
48
|
+
|
|
49
|
+
**Problem**: naive memory stores scan every vector per query (cost grows
|
|
50
|
+
with memory count), return flat results with no structure, and give the
|
|
51
|
+
agent no notion of _why_ a memory is relevant, what supersedes it, or what
|
|
52
|
+
contradicts it.
|
|
53
|
+
|
|
54
|
+
**Approach** — three structures maintained automatically by a durable
|
|
55
|
+
background worker:
|
|
56
|
+
|
|
57
|
+
1. **Topic tree** — memories are routed by embedding similarity into tree
|
|
58
|
+
leaves. Queries descend the tree (beam search on centroids) and only
|
|
59
|
+
score the reached leaves: **search cost grows with depth, not with
|
|
60
|
+
memory count**. A durable worker splits overflowing leaves and merges
|
|
61
|
+
underflowing ones with deterministic algorithms (CF-triplet k-means) —
|
|
62
|
+
same embeddings ⇒ same tree, anywhere.
|
|
63
|
+
2. **Hybrid retrieval** — vector (cosine) + FTS5 lexical legs fused with
|
|
64
|
+
Reciprocal Rank Fusion, optional recency boost, and per-result graph
|
|
65
|
+
context.
|
|
66
|
+
3. **Artifact graph + concepts** — directed `memory_supports` /
|
|
67
|
+
`memory_supersedes` / `memory_contradicts` edges written by structural
|
|
68
|
+
rules, code indexing, the agent, and the optional **dream** job (LLM
|
|
69
|
+
classification of candidates + deterministic concept clustering).
|
|
70
|
+
Retrieval consumes the graph: superseded memories demote, conflicts
|
|
71
|
+
surface, supports ride along as provenance; concept filters give the
|
|
72
|
+
query sovereignty.
|
|
73
|
+
|
|
74
|
+
All structural decisions are **deterministic**; everything is **durable**
|
|
75
|
+
(two SQLite files, resume-safe jobs).
|
|
76
|
+
|
|
77
|
+
## Configuration
|
|
78
|
+
|
|
79
|
+
Pass a config file path or an inline object:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
const dimple = await makeDimple({ config: "./dimple.jsonc" });
|
|
83
|
+
// or
|
|
84
|
+
const dimple = await makeDimple({
|
|
85
|
+
config: {
|
|
86
|
+
storeUrl: "file:./.dimple/dimple.db",
|
|
87
|
+
jobs: { dbUrl: "file:./.dimple/jobs.db" },
|
|
88
|
+
embedding: {
|
|
89
|
+
defaultModel: "minilm",
|
|
90
|
+
models: [
|
|
91
|
+
{
|
|
92
|
+
id: "minilm",
|
|
93
|
+
provider: "transformers",
|
|
94
|
+
model: "Xenova/all-MiniLM-L6-v2",
|
|
95
|
+
dimensions: 384,
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Config sections (`dimple.jsonc`)
|
|
104
|
+
|
|
105
|
+
| Section | Fields | Notes |
|
|
106
|
+
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ |
|
|
107
|
+
| `storeUrl` | string | memory store DB (`file:` or remote libSQL) |
|
|
108
|
+
| `jobs` | `dbUrl`, `execution` (reserved), `concurrency`, `leaseTimeoutMs` (reserved), `maxAttempts` (1–100), `pollIntervalMs` (reserved) | separate DB for the durable queue |
|
|
109
|
+
| `embedding` | `defaultModel` + `models[]` | **required** — see Models below |
|
|
110
|
+
| `topics` | tree shape (`minLeafItems`, `maxLeafItems`, …) | optional, defaults apply |
|
|
111
|
+
| `graph` | graph config | optional |
|
|
112
|
+
| `llm` | `baseURL`, `apiKey`, `modelId`, `temperature` | enables `dream`/`repair` |
|
|
113
|
+
| `logging` | `enabled`, `level`, `format` (`jsonl`\|`pretty`), `file`, `scopes`, `ringSize` | silent by default; `file` = durable JSONL |
|
|
114
|
+
| `defaultK` / `defaultLimit` | numbers | retrieval defaults |
|
|
115
|
+
| `defaultTtlSecs` | number | write TTL default |
|
|
116
|
+
|
|
117
|
+
### Embedding models
|
|
118
|
+
|
|
119
|
+
Config-swappable — any Transformers.js ONNX model (the only tested
|
|
120
|
+
provider):
|
|
121
|
+
|
|
122
|
+
```jsonc
|
|
123
|
+
"embedding": {
|
|
124
|
+
"defaultModel": "bge",
|
|
125
|
+
"models": [
|
|
126
|
+
{ "id": "minilm", "provider": "transformers",
|
|
127
|
+
"model": "Xenova/all-MiniLM-L6-v2", "dimensions": 384 },
|
|
128
|
+
{ "id": "bge", "provider": "transformers",
|
|
129
|
+
"model": "Xenova/bge-small-en-v1.5", "dimensions": 384 },
|
|
130
|
+
{ "id": "bge-base", "provider": "transformers",
|
|
131
|
+
"model": "Xenova/bge-base-en-v1.5", "dimensions": 768 }
|
|
132
|
+
]
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
- `dimensions` must match the model's real output — a mismatch fails typed
|
|
137
|
+
on first use
|
|
138
|
+
- Multiple models coexist: each gets its own vector tables
|
|
139
|
+
(`vec_<dims>_<model>`) and its own topic tree; queries route per model
|
|
140
|
+
- **Per-model runtime options** (transformers provider, all optional):
|
|
141
|
+
`device` (`auto` | `gpu` | `cpu` | `wasm` | `webgpu` | `cuda` | `dml` |
|
|
142
|
+
`coreml` | `webnn` — `cpu`/`wasm` tested; GPU/CUDA needs the onnxruntime
|
|
143
|
+
CUDA libs, fails typed without them), `dtype` (`fp32` | `fp16` | `q8` |
|
|
144
|
+
`int8` | `uint8` | `q4` | `bnb4` | `q4f16` | `q2` | `q2f16` | `q1` |
|
|
145
|
+
`q1f16` — quantizes model WEIGHTS only, never the output vectors;
|
|
146
|
+
`fp32`/`q8`/`int8`/`q4`/`fp16` verified end-to-end, others depend on the
|
|
147
|
+
HF repo shipping that ONNX variant), `pooling` (`mean` | `cls` | `none`),
|
|
148
|
+
`normalize` (boolean), `cacheDir`
|
|
149
|
+
- **DB-level vector quantization** via `vectorType` (`float32` |
|
|
150
|
+
`float16` | `float8` | `float1bit`): the per-memory search vectors are
|
|
151
|
+
stored as int8/bfloat16/binary blobs and searched with libSQL's native
|
|
152
|
+
`vector_distance_cos` — `float8` verified end-to-end at **395 B vs
|
|
153
|
+
1536 B (4× smaller)** for 384-dim. The topic tree's centroid math and
|
|
154
|
+
`memories.embedding` always stay float32.
|
|
155
|
+
- **Switching models**: set `defaultModel` to a configured model (multi-
|
|
156
|
+
model configs coexist — each model gets its own tree + `vec_<dims>_<model>`
|
|
157
|
+
tables). After a restart the new default handles all writes/queries;
|
|
158
|
+
the old model's tree stays intact. Existing memories are backfilled via
|
|
159
|
+
the durable `memory.embed` job (re-embed + re-route into the new tree,
|
|
160
|
+
idempotent/resume-safe) — the store-level switch (`setActiveModel` +
|
|
161
|
+
`findStale`) drives it, reachable through the `{ core: … }` path. Until
|
|
162
|
+
backfilled, old memories remain in the old tree (queries descend the
|
|
163
|
+
ACTIVE model's tree), and the runtime is bound to one active model at a
|
|
164
|
+
time (per-model reads for another model fail typed with
|
|
165
|
+
`topics.ModelMismatch`).
|
|
166
|
+
- Vectors are always stored as float32 (`F32_BLOB`, 4 B/dim: 384 = 1536 B,
|
|
167
|
+
768 = 3072 B) regardless of `dtype`
|
|
168
|
+
|
|
169
|
+
### Power users: `{ core: … }`
|
|
170
|
+
|
|
171
|
+
Instead of a config, pass full `CoreOptions` (store/jobs URLs, models,
|
|
172
|
+
`defaultModelId`, `embedderFor` factory, topics/graph/logging/llm,
|
|
173
|
+
`concurrency`, `maxAttempts`, `extraDefs`/`extraHandlers` for custom durable
|
|
174
|
+
jobs — register `{ name: "my.job", handler }` and enqueue it via the
|
|
175
|
+
runtime; `extraHandlers` entries override built-ins by name). This is the
|
|
176
|
+
injection seam used by tests (synthetic embedders).
|
|
177
|
+
|
|
178
|
+
## API surface
|
|
179
|
+
|
|
180
|
+
### `write(input, options?)`
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
await dimple.write({
|
|
184
|
+
logicalKey: "deploy-1", // required, non-empty
|
|
185
|
+
content: "…", // required
|
|
186
|
+
namespace?: "default",
|
|
187
|
+
contentType?: "text/plain",
|
|
188
|
+
ttlSecs?: 604800,
|
|
189
|
+
metadata?: "{\"json\":true}", // JSON string
|
|
190
|
+
typedMetadata?: { … },
|
|
191
|
+
}, { embed: true }); // embed + topic-route (default); false for externally-managed embeddings
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Returns the persisted `MemoryUnit`: `{ id, logicalKey, content,
|
|
195
|
+
contentType, state: "active" | "superseded" | …, createdAt, ttlSecs,
|
|
196
|
+
namespace, metadata, embedding? }` (re-fetched after embedding). Duplicate
|
|
197
|
+
active `logicalKey` → `Sdk.DuplicateKey` (carries `logicalKey`/`namespace`).
|
|
198
|
+
|
|
199
|
+
**Memory expiry**: `ttlSecs` (or the config `defaultTtlSecs`) schedules
|
|
200
|
+
expiry; a sweep job retires expired memories in the background.
|
|
201
|
+
|
|
202
|
+
### `get(id)` / `forget(id)`
|
|
203
|
+
|
|
204
|
+
Fetch one memory; soft-delete (cascades edges, rebalances the tree).
|
|
205
|
+
|
|
206
|
+
### `query(text, options?)`
|
|
207
|
+
|
|
208
|
+
```ts
|
|
209
|
+
await dimple.query("how does the pipeline deploy", {
|
|
210
|
+
limit?: 10,
|
|
211
|
+
k?: 60, // RRF smoothing
|
|
212
|
+
namespace?: "default",
|
|
213
|
+
profile?: {
|
|
214
|
+
recencyBoost?: 0, // 0 = off; hyperbolically decays with age
|
|
215
|
+
modeWeights?: { vector?: 0.5, fts?: 0.5 },
|
|
216
|
+
conceptFilter?: string[], // concept ids (query sovereignty)
|
|
217
|
+
} | "hybrid" | "semantic" | "keyword",
|
|
218
|
+
topics?: { topicKeys?: string[] },
|
|
219
|
+
});
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Returns `AugmentedMemory[]` — each entry carries the memory, the fused
|
|
223
|
+
score, its 1-based `rank`, and the graph context (`supersededBy`,
|
|
224
|
+
`conflicts`, `supportedBy`). Zero-result queries resolve to `[]` (the
|
|
225
|
+
absent-vector-table case is an empty dense leg, never an error).
|
|
226
|
+
|
|
227
|
+
### `index(options)` — codebase ingestion
|
|
228
|
+
|
|
229
|
+
```ts
|
|
230
|
+
await dimple.index({
|
|
231
|
+
paths: ["./src", "./packages"], // dirs (recursive) and/or files
|
|
232
|
+
keyPrefix?: "code-",
|
|
233
|
+
namespace?: "default",
|
|
234
|
+
embed?: true,
|
|
235
|
+
maxFileBytes?: 64 * 1024,
|
|
236
|
+
maxChunkBytes?: 8 * 1024,
|
|
237
|
+
extensions?: [".ts", ".tsx", ".js", ".jsx", …],
|
|
238
|
+
});
|
|
239
|
+
// → { files, indexed, skipped, unsupported, failed: [{ filePath, message }] }
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
- Tree-sitter parse (TypeScript/JavaScript/TSX/JSX) → the extractor
|
|
243
|
+
produces one **file unit** per file (with its import graph in metadata)
|
|
244
|
+
and one **chunk unit** per definition (function/class/method/interface/
|
|
245
|
+
type/enum/variable, with kind/name/line-range/docstring metadata)
|
|
246
|
+
- Every unit embeds + topic-routes (unless `embed: false`)
|
|
247
|
+
- AST-derived graph: relative imports resolve to indexed files and
|
|
248
|
+
class→method containment become **confirmed `memory_supports` edges**
|
|
249
|
+
(`source: "rule"`)
|
|
250
|
+
- **Idempotent**: keys are cwd-anchored + deterministic — re-indexing the
|
|
251
|
+
same tree skips existing units
|
|
252
|
+
- Per-file failures are collected, never fatal
|
|
253
|
+
|
|
254
|
+
### `updateEmbedding(id, embedding, modelKey?)` / `embed(text)`
|
|
255
|
+
|
|
256
|
+
Attach an externally-computed embedding (completes the `{ embed: false }`
|
|
257
|
+
workflow — the memory becomes semantically retrievable + topic-routed), or
|
|
258
|
+
embed text with the SDK's own pipeline.
|
|
259
|
+
|
|
260
|
+
### `dream(modelKey)` / `repair(modelKey)`
|
|
261
|
+
|
|
262
|
+
Run the consolidation job (concept clustering + LLM edge classification;
|
|
263
|
+
requires the `llm` config section) or the graph repair pass. Each call
|
|
264
|
+
gets a fresh run id (always executes). Failures are typed:
|
|
265
|
+
`Sdk.LLMNotConfigured`, `Sdk.JobFailed`.
|
|
266
|
+
|
|
267
|
+
### `explain({ kind: "memory" | "topic" | "concept", id })`
|
|
268
|
+
|
|
269
|
+
Graph neighborhood traversal — `{ nodes: [{ kind, id, depth, edgeKind }] }`.
|
|
270
|
+
|
|
271
|
+
### `health(modelKey)` / `graph.*`
|
|
272
|
+
|
|
273
|
+
- `health` — topic-tree invariant sweep (`warnCount` + violation strings)
|
|
274
|
+
- `graph.addEdge({ fromMemoryId, toMemoryId, edgeKind: "memory_supports" |
|
|
275
|
+
"memory_supersedes" | "memory_contradicts", source: "rule" | "llm" |
|
|
276
|
+
"agent", weight?, confidence?, confirmed? })` — explicit edges; typed
|
|
277
|
+
validation (no self-loops, known kinds, weight > 0, confidence ∈ [0,1])
|
|
278
|
+
- `graph.candidates()` — pending structural candidates + isolated count
|
|
279
|
+
- `graph.coverage(modelKey)` — coverage %, pending, concept count
|
|
280
|
+
|
|
281
|
+
### `dispose()`
|
|
282
|
+
|
|
283
|
+
Stops the worker, closes sinks + the store. Idempotent; post-dispose calls
|
|
284
|
+
reject with a closed `Sdk.StoreFailure`.
|
|
285
|
+
|
|
286
|
+
## Errors — the closed hierarchy
|
|
287
|
+
|
|
288
|
+
Every promise rejects with typed errors from one family (never raw Effect
|
|
289
|
+
internals). Export surface: `ConfigError`, `NotFoundError`,
|
|
290
|
+
`DuplicateKeyError` (with `logicalKey`/`namespace`), `DuplicateEdgeError`,
|
|
291
|
+
`LLMNotConfiguredError`, `JobFailedError`, `StoreFailure`, `InputError`,
|
|
292
|
+
plus the `remapError` helper (maps unknown failures onto the hierarchy).
|
|
293
|
+
Also importable from the `@ponraaj/dimple-sdk/errors` subpath.
|
|
294
|
+
|
|
295
|
+
**Type exports** (all importable from the package root): `Dimple` (the
|
|
296
|
+
facade interface), `MakeDimpleOptions`, `ConfigInput`, `QueryOptions`,
|
|
297
|
+
`IndexOptions`, `IndexResult`, plus the error classes and the `DimpleError`
|
|
298
|
+
union type. Errors also carry stable `_tag` values (`Sdk.DuplicateKey`,
|
|
299
|
+
`Sdk.NotFound`, `Sdk.InputError`, `Sdk.JobFailed`, `Sdk.LLMNotConfigured`,
|
|
300
|
+
`Sdk.StoreFailure`, …) — the recommended discriminator.
|
|
301
|
+
|
|
302
|
+
## Background jobs (what runs while you work)
|
|
303
|
+
|
|
304
|
+
| Job | Trigger | Work |
|
|
305
|
+
| ------------------------------------------------- | ------------------- | -------------------------------------------------------------------------------------------------------------------------- |
|
|
306
|
+
| `topic.split` / `topic.merge` / `topic.rebalance` | automatic per write | keep every leaf within its size budget (deterministic splits, donor borrow, nearest-sibling merge, uneven-split rebalance) |
|
|
307
|
+
| `memory.embed` | model backfill | re-embed a memory with a target model + route into its tree |
|
|
308
|
+
| `memory.dream` | `dream()` | concept clustering + LLM edge classification |
|
|
309
|
+
| `graph.repair` | `repair()` | graph invariant repair |
|
|
310
|
+
| `audit.write` | query path | async audit-log writes (never blocks queries) — one FUSED row per hybrid query with the per-leg breakdown |
|
|
311
|
+
|
|
312
|
+
Jobs are durable (leased, retried up to `maxAttempts`, resume-safe across
|
|
313
|
+
processes). Every `makeDimple` boots the worker; `dispose` stops it.
|
|
314
|
+
|
|
315
|
+
Every `query` also writes a retrieval audit row (query, namespace, mode,
|
|
316
|
+
per-leg counts/scores, sources) into `retrieval_log` — machine-readable
|
|
317
|
+
query history, persisted asynchronously (never blocks the query).
|
|
318
|
+
|
|
319
|
+
## Storage & logs
|
|
320
|
+
|
|
321
|
+
| What | Where |
|
|
322
|
+
| ------------------------------ | -------------------------------------------------------------------------------------------- |
|
|
323
|
+
| Memories, tree, graph, audit | `storeUrl` (`file:…`) |
|
|
324
|
+
| Durable queue + workflow state | `jobs.dbUrl` (separate DB) |
|
|
325
|
+
| Logs | stdout + in-memory ring (4096) by default; durable JSONL **only** when `logging.file` is set |
|
|
326
|
+
| Grammar WASMs | `~/.cache/dimple/tree-sitter/` (downloaded once, offline after) |
|
|
327
|
+
| ONNX model | bun/npm cache (~90 MB, first use) |
|
|
328
|
+
|
|
329
|
+
## Notes
|
|
330
|
+
|
|
331
|
+
- Determinism: same data + same config ⇒ same tree, same clusters, same
|
|
332
|
+
audit hashes — reproducible across machines and runs
|
|
333
|
+
- `migrationFolder` is accepted in `makeDimple` options for advanced users;
|
|
334
|
+
the default path uses migrations embedded at build time (nothing to ship)
|
package/dist/index.js
CHANGED
|
@@ -659,7 +659,35 @@ var EmbeddingModelConfig = Schema6.Struct({
|
|
|
659
659
|
dimensions: Schema6.Number,
|
|
660
660
|
vectorType: Schema6.optional(Schema6.String),
|
|
661
661
|
apiKey: Schema6.optional(Schema6.String),
|
|
662
|
-
indexConfig: Schema6.optional(IndexConfig)
|
|
662
|
+
indexConfig: Schema6.optional(IndexConfig),
|
|
663
|
+
device: Schema6.optional(Schema6.Literals([
|
|
664
|
+
"auto",
|
|
665
|
+
"gpu",
|
|
666
|
+
"cpu",
|
|
667
|
+
"wasm",
|
|
668
|
+
"webgpu",
|
|
669
|
+
"cuda",
|
|
670
|
+
"dml",
|
|
671
|
+
"coreml",
|
|
672
|
+
"webnn"
|
|
673
|
+
])),
|
|
674
|
+
dtype: Schema6.optional(Schema6.Literals([
|
|
675
|
+
"fp32",
|
|
676
|
+
"fp16",
|
|
677
|
+
"q8",
|
|
678
|
+
"int8",
|
|
679
|
+
"uint8",
|
|
680
|
+
"q4",
|
|
681
|
+
"bnb4",
|
|
682
|
+
"q4f16",
|
|
683
|
+
"q2",
|
|
684
|
+
"q2f16",
|
|
685
|
+
"q1",
|
|
686
|
+
"q1f16"
|
|
687
|
+
])),
|
|
688
|
+
pooling: Schema6.optional(Schema6.Literals(["mean", "cls", "none"])),
|
|
689
|
+
normalize: Schema6.optional(Schema6.Boolean),
|
|
690
|
+
cacheDir: Schema6.optional(Schema6.String)
|
|
663
691
|
});
|
|
664
692
|
var EmbeddingConfig = Schema6.Struct({
|
|
665
693
|
defaultModel: Schema6.String,
|
|
@@ -1410,13 +1438,11 @@ var makeBackend = (url, client, db, migrationFolder) => ({
|
|
|
1410
1438
|
ensureTopicVectorTable: (model) => Effect9.tryPromise({
|
|
1411
1439
|
try: () => {
|
|
1412
1440
|
const tableName = topicVectorTableName(model);
|
|
1413
|
-
const vecType = model.vectorType ?? "float32";
|
|
1414
|
-
const sqlType = VECTOR_SQL_TYPES[vecType] ?? "F32_BLOB";
|
|
1415
1441
|
const dims = model.dimensions;
|
|
1416
1442
|
const sql3 = `
|
|
1417
1443
|
CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
1418
1444
|
topic_key TEXT PRIMARY KEY,
|
|
1419
|
-
sum_vector
|
|
1445
|
+
sum_vector F32_BLOB(${dims}) NOT NULL,
|
|
1420
1446
|
model_key TEXT NOT NULL
|
|
1421
1447
|
);
|
|
1422
1448
|
`;
|
|
@@ -1428,9 +1454,8 @@ var makeBackend = (url, client, db, migrationFolder) => ({
|
|
|
1428
1454
|
try: () => {
|
|
1429
1455
|
const tableName = topicVectorTableName(model);
|
|
1430
1456
|
const vecJson = JSON.stringify([...sumVector]);
|
|
1431
|
-
const convertFn = VECTOR_CONVERT_FNS[model.vectorType ?? "float32"] ?? "vector32";
|
|
1432
1457
|
return client.execute({
|
|
1433
|
-
sql: `INSERT OR REPLACE INTO ${tableName} (topic_key, sum_vector, model_key) VALUES (?,
|
|
1458
|
+
sql: `INSERT OR REPLACE INTO ${tableName} (topic_key, sum_vector, model_key) VALUES (?, vector32(?), ?)`,
|
|
1434
1459
|
args: [topicKey, vecJson, model.id]
|
|
1435
1460
|
});
|
|
1436
1461
|
},
|
|
@@ -1462,16 +1487,18 @@ var makeBackend = (url, client, db, migrationFolder) => ({
|
|
|
1462
1487
|
return [];
|
|
1463
1488
|
const tableName = vectorTableName(model);
|
|
1464
1489
|
const placeholders = memoryIds.map(() => "?").join(", ");
|
|
1490
|
+
const quantized = (model.vectorType ?? "float32") !== "float32";
|
|
1491
|
+
const extractCol = quantized ? "vector_extract(embedding) AS embedding" : "embedding";
|
|
1465
1492
|
const rows = yield* Effect9.tryPromise({
|
|
1466
1493
|
try: () => client.execute({
|
|
1467
|
-
sql: `SELECT memory_id,
|
|
1494
|
+
sql: `SELECT memory_id, ${extractCol} FROM ${tableName} WHERE memory_id IN (${placeholders})`,
|
|
1468
1495
|
args: [...memoryIds]
|
|
1469
1496
|
}).then((rs) => rs.rows),
|
|
1470
1497
|
catch: (cause) => new StoreError({ message: `DbBackend.fetchMemoryVectors: ${cause}` })
|
|
1471
1498
|
});
|
|
1472
1499
|
return rows.map((row) => ({
|
|
1473
1500
|
memoryId: row.memory_id,
|
|
1474
|
-
vector: decodeVectorBlob(row.embedding)
|
|
1501
|
+
vector: quantized ? JSON.parse(String(row.embedding)) : decodeVectorBlob(row.embedding)
|
|
1475
1502
|
}));
|
|
1476
1503
|
}),
|
|
1477
1504
|
listMemoryIds: (model) => Effect9.gen(function* () {
|
|
@@ -3237,22 +3264,15 @@ import { and as and4, asc as asc3, eq as eq4, inArray as inArray2, sql as sql4 }
|
|
|
3237
3264
|
// ../store/src/topics/topic-vectors.ts
|
|
3238
3265
|
import { sql as sql3 } from "drizzle-orm";
|
|
3239
3266
|
var topicVectorTableName2 = (model) => `topic_vec_${model.dimensions}_${model.id.replace(/[^a-zA-Z0-9_]/g, "_")}`;
|
|
3240
|
-
var VECTOR_CONVERT_FNS2 = {
|
|
3241
|
-
float32: "vector32",
|
|
3242
|
-
float16: "vector16",
|
|
3243
|
-
float8: "vector8",
|
|
3244
|
-
float1bit: "vector1bit"
|
|
3245
|
-
};
|
|
3246
3267
|
var deleteTopicVectorRow = async (runner, model, topicKey) => {
|
|
3247
3268
|
const table = topicVectorTableName2(model);
|
|
3248
3269
|
await runner.run(sql3`DELETE FROM ${sql3.raw(table)} WHERE topic_key = ${topicKey}`);
|
|
3249
3270
|
};
|
|
3250
3271
|
var writeTopicVector = async (runner, model, topicKey, ls) => {
|
|
3251
3272
|
const table = topicVectorTableName2(model);
|
|
3252
|
-
const convertFn = VECTOR_CONVERT_FNS2[model.vectorType ?? "float32"] ?? "vector32";
|
|
3253
3273
|
await runner.run(sql3`
|
|
3254
3274
|
INSERT OR REPLACE INTO ${sql3.raw(table)} (topic_key, sum_vector, model_key)
|
|
3255
|
-
VALUES (${topicKey},
|
|
3275
|
+
VALUES (${topicKey}, vector32(${JSON.stringify([...ls])}), ${model.id})
|
|
3256
3276
|
`);
|
|
3257
3277
|
};
|
|
3258
3278
|
|
|
@@ -6807,7 +6827,15 @@ var DISABLED_LOGGING2 = {
|
|
|
6807
6827
|
format: "jsonl",
|
|
6808
6828
|
ringSize: 4096
|
|
6809
6829
|
};
|
|
6810
|
-
var embedderFromConfig = (model) => model.provider === "transformers" ? Effect39.succeed(makeTransformersModel({
|
|
6830
|
+
var embedderFromConfig = (model) => model.provider === "transformers" ? Effect39.succeed(makeTransformersModel({
|
|
6831
|
+
modelId: model.model,
|
|
6832
|
+
dimensions: model.dimensions,
|
|
6833
|
+
...model.device !== undefined ? { device: model.device } : {},
|
|
6834
|
+
...model.dtype !== undefined ? { dtype: model.dtype } : {},
|
|
6835
|
+
...model.pooling !== undefined ? { pooling: model.pooling } : {},
|
|
6836
|
+
...model.normalize !== undefined ? { normalize: model.normalize } : {},
|
|
6837
|
+
...model.cacheDir !== undefined ? { cacheDir: model.cacheDir } : {}
|
|
6838
|
+
})) : model.provider === "openai" ? model.apiKey === undefined ? Effect39.fail(new EmbeddingError({ message: `OpenAI model ${model.id} requires an apiKey` })) : Effect39.succeed(makeOpenAIModel({
|
|
6811
6839
|
modelId: model.id,
|
|
6812
6840
|
model: model.model,
|
|
6813
6841
|
dimensions: model.dimensions,
|
|
@@ -18,6 +18,15 @@ export declare const EmbeddingModelConfig: Schema.Struct<{
|
|
|
18
18
|
readonly maxNeighbors: Schema.optional<Schema.Number>;
|
|
19
19
|
readonly searchL: Schema.optional<Schema.Number>;
|
|
20
20
|
}>>;
|
|
21
|
+
/** Transformers.js runtime options — per-model, all optional (defaults:
|
|
22
|
+
* device "cpu", dtype "q8", pooling "mean", normalize true). `dtype`
|
|
23
|
+
* quantizes the MODEL WEIGHTS (inference memory/speed), never the
|
|
24
|
+
* output vectors; unsupported devices fail typed at first use. */
|
|
25
|
+
readonly device: Schema.optional<Schema.Literals<readonly ["auto", "gpu", "cpu", "wasm", "webgpu", "cuda", "dml", "coreml", "webnn"]>>;
|
|
26
|
+
readonly dtype: Schema.optional<Schema.Literals<readonly ["fp32", "fp16", "q8", "int8", "uint8", "q4", "bnb4", "q4f16", "q2", "q2f16", "q1", "q1f16"]>>;
|
|
27
|
+
readonly pooling: Schema.optional<Schema.Literals<readonly ["mean", "cls", "none"]>>;
|
|
28
|
+
readonly normalize: Schema.optional<Schema.Boolean>;
|
|
29
|
+
readonly cacheDir: Schema.optional<Schema.String>;
|
|
21
30
|
}>;
|
|
22
31
|
export interface EmbeddingModelConfig extends Schema.Schema.Type<typeof EmbeddingModelConfig> {
|
|
23
32
|
}
|
|
@@ -35,6 +44,15 @@ export declare const EmbeddingConfig: Schema.Struct<{
|
|
|
35
44
|
readonly maxNeighbors: Schema.optional<Schema.Number>;
|
|
36
45
|
readonly searchL: Schema.optional<Schema.Number>;
|
|
37
46
|
}>>;
|
|
47
|
+
/** Transformers.js runtime options — per-model, all optional (defaults:
|
|
48
|
+
* device "cpu", dtype "q8", pooling "mean", normalize true). `dtype`
|
|
49
|
+
* quantizes the MODEL WEIGHTS (inference memory/speed), never the
|
|
50
|
+
* output vectors; unsupported devices fail typed at first use. */
|
|
51
|
+
readonly device: Schema.optional<Schema.Literals<readonly ["auto", "gpu", "cpu", "wasm", "webgpu", "cuda", "dml", "coreml", "webnn"]>>;
|
|
52
|
+
readonly dtype: Schema.optional<Schema.Literals<readonly ["fp32", "fp16", "q8", "int8", "uint8", "q4", "bnb4", "q4f16", "q2", "q2f16", "q1", "q1f16"]>>;
|
|
53
|
+
readonly pooling: Schema.optional<Schema.Literals<readonly ["mean", "cls", "none"]>>;
|
|
54
|
+
readonly normalize: Schema.optional<Schema.Boolean>;
|
|
55
|
+
readonly cacheDir: Schema.optional<Schema.String>;
|
|
38
56
|
}>>;
|
|
39
57
|
}>;
|
|
40
58
|
export interface EmbeddingConfig extends Schema.Schema.Type<typeof EmbeddingConfig> {
|
|
@@ -115,6 +133,15 @@ export declare const DimpleConfig: Schema.Struct<{
|
|
|
115
133
|
readonly maxNeighbors: Schema.optional<Schema.Number>;
|
|
116
134
|
readonly searchL: Schema.optional<Schema.Number>;
|
|
117
135
|
}>>;
|
|
136
|
+
/** Transformers.js runtime options — per-model, all optional (defaults:
|
|
137
|
+
* device "cpu", dtype "q8", pooling "mean", normalize true). `dtype`
|
|
138
|
+
* quantizes the MODEL WEIGHTS (inference memory/speed), never the
|
|
139
|
+
* output vectors; unsupported devices fail typed at first use. */
|
|
140
|
+
readonly device: Schema.optional<Schema.Literals<readonly ["auto", "gpu", "cpu", "wasm", "webgpu", "cuda", "dml", "coreml", "webnn"]>>;
|
|
141
|
+
readonly dtype: Schema.optional<Schema.Literals<readonly ["fp32", "fp16", "q8", "int8", "uint8", "q4", "bnb4", "q4f16", "q2", "q2f16", "q1", "q1f16"]>>;
|
|
142
|
+
readonly pooling: Schema.optional<Schema.Literals<readonly ["mean", "cls", "none"]>>;
|
|
143
|
+
readonly normalize: Schema.optional<Schema.Boolean>;
|
|
144
|
+
readonly cacheDir: Schema.optional<Schema.String>;
|
|
118
145
|
}>>;
|
|
119
146
|
}>>;
|
|
120
147
|
readonly jobs: Schema.optional<Schema.Struct<{
|
|
@@ -4,7 +4,7 @@ import { Service, type EmbeddingModel } from "./provider.ts";
|
|
|
4
4
|
interface TransformersOptions {
|
|
5
5
|
readonly modelId?: string;
|
|
6
6
|
readonly device?: DeviceType;
|
|
7
|
-
readonly dtype?: "fp32" | "fp16" | "q8" | "q4";
|
|
7
|
+
readonly dtype?: "fp32" | "fp16" | "q8" | "int8" | "uint8" | "q4" | "bnb4" | "q4f16" | "q2" | "q2f16" | "q1" | "q1f16";
|
|
8
8
|
readonly pooling?: "mean" | "cls" | "none";
|
|
9
9
|
readonly normalize?: boolean;
|
|
10
10
|
readonly cacheDir?: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ponraaj/dimple-sdk",
|
|
3
|
-
"version": "0.0.0-alpha.
|
|
3
|
+
"version": "0.0.0-alpha.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"files": [
|
|
6
6
|
"dist"
|
|
@@ -39,6 +39,16 @@
|
|
|
39
39
|
"web-tree-sitter": "^0.26.9",
|
|
40
40
|
"zod": "^4.4.3"
|
|
41
41
|
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@dimple/config": "0.0.0-alpha.1",
|
|
44
|
+
"@dimple/core": "0.0.0-alpha.1",
|
|
45
|
+
"@dimple/embedder": "0.0.0-alpha.1",
|
|
46
|
+
"@dimple/extractor": "0.0.0-alpha.1",
|
|
47
|
+
"@dimple/parser": "0.0.0-alpha.1",
|
|
48
|
+
"@dimple/retriever": "0.0.0-alpha.1",
|
|
49
|
+
"@dimple/store": "0.0.0-alpha.1",
|
|
50
|
+
"@dimple/workflow": "0.0.0-alpha.1"
|
|
51
|
+
},
|
|
42
52
|
"peerDependencies": {
|
|
43
53
|
"typescript": "^5"
|
|
44
54
|
}
|