@xdarkicex/openclaw-memory-libravdb 1.3.5

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.
Files changed (80) hide show
  1. package/README.md +46 -0
  2. package/docs/README.md +14 -0
  3. package/docs/architecture-decisions/README.md +6 -0
  4. package/docs/architecture-decisions/adr-001-onnx-over-ollama.md +21 -0
  5. package/docs/architecture-decisions/adr-002-libravdb-over-lancedb.md +19 -0
  6. package/docs/architecture-decisions/adr-003-convex-gating-over-threshold.md +27 -0
  7. package/docs/architecture-decisions/adr-004-sidecar-over-native-ts.md +21 -0
  8. package/docs/architecture.md +188 -0
  9. package/docs/contributing.md +76 -0
  10. package/docs/dependencies.md +38 -0
  11. package/docs/embedding-profiles.md +42 -0
  12. package/docs/gating.md +329 -0
  13. package/docs/implementation.md +381 -0
  14. package/docs/installation.md +272 -0
  15. package/docs/mathematics.md +695 -0
  16. package/docs/models.md +63 -0
  17. package/docs/problem.md +64 -0
  18. package/docs/security.md +86 -0
  19. package/openclaw.plugin.json +84 -0
  20. package/package.json +41 -0
  21. package/scripts/build-sidecar.sh +30 -0
  22. package/scripts/postinstall.js +169 -0
  23. package/scripts/setup.sh +20 -0
  24. package/scripts/setup.ts +505 -0
  25. package/scripts/sidecar-release.d.ts +4 -0
  26. package/scripts/sidecar-release.js +17 -0
  27. package/sidecar/cmd/inspect_onnx/main.go +105 -0
  28. package/sidecar/compact/gate.go +273 -0
  29. package/sidecar/compact/gate_test.go +85 -0
  30. package/sidecar/compact/summarize.go +345 -0
  31. package/sidecar/compact/summarize_test.go +319 -0
  32. package/sidecar/compact/tokens.go +11 -0
  33. package/sidecar/config/config.go +119 -0
  34. package/sidecar/config/config_test.go +75 -0
  35. package/sidecar/embed/engine.go +696 -0
  36. package/sidecar/embed/engine_test.go +349 -0
  37. package/sidecar/embed/matryoshka.go +93 -0
  38. package/sidecar/embed/matryoshka_test.go +150 -0
  39. package/sidecar/embed/onnx_local.go +319 -0
  40. package/sidecar/embed/onnx_local_test.go +159 -0
  41. package/sidecar/embed/profile_contract_test.go +71 -0
  42. package/sidecar/embed/profile_eval_test.go +923 -0
  43. package/sidecar/embed/profiles.go +39 -0
  44. package/sidecar/go.mod +21 -0
  45. package/sidecar/go.sum +30 -0
  46. package/sidecar/health/check.go +33 -0
  47. package/sidecar/health/check_test.go +55 -0
  48. package/sidecar/main.go +151 -0
  49. package/sidecar/model/encoder.go +222 -0
  50. package/sidecar/model/registry.go +262 -0
  51. package/sidecar/model/registry_test.go +102 -0
  52. package/sidecar/model/seq2seq.go +133 -0
  53. package/sidecar/server/rpc.go +343 -0
  54. package/sidecar/server/rpc_test.go +350 -0
  55. package/sidecar/server/transport.go +160 -0
  56. package/sidecar/store/libravdb.go +676 -0
  57. package/sidecar/store/libravdb_test.go +472 -0
  58. package/sidecar/summarize/engine.go +360 -0
  59. package/sidecar/summarize/engine_test.go +148 -0
  60. package/sidecar/summarize/onnx_local.go +494 -0
  61. package/sidecar/summarize/onnx_local_test.go +48 -0
  62. package/sidecar/summarize/profiles.go +52 -0
  63. package/sidecar/summarize/tokenizer.go +13 -0
  64. package/sidecar/summarize/tokenizer_hf.go +76 -0
  65. package/sidecar/summarize/util.go +13 -0
  66. package/src/cli.ts +205 -0
  67. package/src/context-engine.ts +195 -0
  68. package/src/index.ts +27 -0
  69. package/src/memory-provider.ts +24 -0
  70. package/src/openclaw-plugin-sdk.d.ts +53 -0
  71. package/src/plugin-runtime.ts +67 -0
  72. package/src/recall-cache.ts +34 -0
  73. package/src/recall-utils.ts +22 -0
  74. package/src/rpc.ts +84 -0
  75. package/src/scoring.ts +58 -0
  76. package/src/sidecar.ts +506 -0
  77. package/src/tokens.ts +36 -0
  78. package/src/types.ts +146 -0
  79. package/tsconfig.json +20 -0
  80. package/tsconfig.tests.json +12 -0
@@ -0,0 +1,349 @@
1
+ package embed
2
+
3
+ import (
4
+ "context"
5
+ "errors"
6
+ "testing"
7
+
8
+ "github.com/sugarme/tokenizer"
9
+ )
10
+
11
+ type fakeMiniLMModel struct {
12
+ vector []float32
13
+ err error
14
+ }
15
+
16
+ func (m fakeMiniLMModel) Compute(_ string, _ bool) ([]float32, error) {
17
+ if m.err != nil {
18
+ return nil, m.err
19
+ }
20
+ return append([]float32(nil), m.vector...), nil
21
+ }
22
+
23
+ func (m fakeMiniLMModel) TokenCount(_ string, _ bool) (int, error) {
24
+ if m.err != nil {
25
+ return 0, m.err
26
+ }
27
+ return 4, nil
28
+ }
29
+
30
+ func (m fakeMiniLMModel) Encode(_ string, _ bool) (*tokenizer.Encoding, error) {
31
+ if m.err != nil {
32
+ return nil, m.err
33
+ }
34
+ return &tokenizer.Encoding{
35
+ Ids: []int{1, 2, 3, 4},
36
+ TypeIds: []int{0, 0, 0, 0},
37
+ AttentionMask: []int{1, 1, 1, 1},
38
+ Tokens: []string{"a", "b", "c", "d"},
39
+ Offsets: [][]int{{0, 1}, {1, 2}, {2, 3}, {3, 4}},
40
+ Words: []int{0, 1, 2, 3},
41
+ }, nil
42
+ }
43
+
44
+ func (m fakeMiniLMModel) ComputeEncoding(_ tokenizer.Encoding) ([]float32, error) {
45
+ return m.Compute("", true)
46
+ }
47
+
48
+ func TestNewUnavailableIsNotReady(t *testing.T) {
49
+ engine := NewUnavailable("missing runtime")
50
+ if engine.Ready() {
51
+ t.Fatalf("expected unavailable engine to be not ready")
52
+ }
53
+ if engine.Dimensions() != DefaultDimensions {
54
+ t.Fatalf("expected dimensions %d, got %d", DefaultDimensions, engine.Dimensions())
55
+ }
56
+ }
57
+
58
+ func TestNewPrimaryRequiresRuntimePath(t *testing.T) {
59
+ originalResolver := resolveBundledSpec
60
+ originalFactory := newONNXLocalBackend
61
+ t.Cleanup(func() {
62
+ resolveBundledSpec = originalResolver
63
+ newONNXLocalBackend = originalFactory
64
+ })
65
+
66
+ resolveBundledSpec = func(cfg Config) (onnxLocalSpec, error) {
67
+ return onnxLocalSpec{}, errors.New("missing bundled runtime")
68
+ }
69
+ engine := NewPrimary("")
70
+ if !engine.Ready() {
71
+ t.Fatalf("expected missing runtime path to fall back to a ready local embedder")
72
+ }
73
+ if engine.Mode() != "fallback" {
74
+ t.Fatalf("expected fallback mode, got %q", engine.Mode())
75
+ }
76
+
77
+ resolveBundledSpec = func(cfg Config) (onnxLocalSpec, error) {
78
+ if cfg.RuntimePath != "/tmp/libonnxruntime.dylib" {
79
+ t.Fatalf("unexpected runtime path %q", cfg.RuntimePath)
80
+ }
81
+ if cfg.Profile != DefaultEmbeddingProfile {
82
+ t.Fatalf("expected default profile %q, got %q", DefaultEmbeddingProfile, cfg.Profile)
83
+ }
84
+ return onnxLocalSpec{RuntimePath: cfg.RuntimePath, Dimensions: 768, Normalize: true, Profile: buildProfile(Profile{
85
+ Backend: "bundled",
86
+ Family: DefaultEmbeddingProfile,
87
+ Dimensions: 768,
88
+ Normalize: true,
89
+ })}, nil
90
+ }
91
+ newONNXLocalBackend = func(spec onnxLocalSpec) (embeddingBackend, error) {
92
+ return fakeONNXLocalBackend{vector: make([]float32, 768)}, nil
93
+ }
94
+ engine = NewPrimary("/tmp/libonnxruntime.dylib")
95
+ if !engine.Ready() {
96
+ t.Fatalf("expected non-empty runtime path to mark engine ready")
97
+ }
98
+ if engine.Mode() != "primary" {
99
+ t.Fatalf("expected primary mode, got %q", engine.Mode())
100
+ }
101
+ if engine.Dimensions() != 768 {
102
+ t.Fatalf("expected dimensions 768, got %d", engine.Dimensions())
103
+ }
104
+ }
105
+
106
+ func TestBundledBackendUsesRealModelFactory(t *testing.T) {
107
+ originalResolver := resolveBundledSpec
108
+ originalFactory := newONNXLocalBackend
109
+ t.Cleanup(func() {
110
+ resolveBundledSpec = originalResolver
111
+ newONNXLocalBackend = originalFactory
112
+ })
113
+
114
+ resolveBundledSpec = func(cfg Config) (onnxLocalSpec, error) {
115
+ return onnxLocalSpec{
116
+ RuntimePath: cfg.RuntimePath,
117
+ Dimensions: 384,
118
+ Normalize: true,
119
+ Profile: buildProfile(Profile{
120
+ Backend: "bundled",
121
+ Family: "all-minilm-l6-v2",
122
+ Dimensions: 384,
123
+ Normalize: true,
124
+ }),
125
+ }, nil
126
+ }
127
+ newONNXLocalBackend = func(spec onnxLocalSpec) (embeddingBackend, error) {
128
+ if spec.RuntimePath != "/opt/onnx/libonnxruntime.so" {
129
+ t.Fatalf("unexpected runtime path %q", spec.RuntimePath)
130
+ }
131
+ vec := make([]float32, 384)
132
+ vec[0] = 3
133
+ vec[1] = 4
134
+ return miniLMBackend{
135
+ model: fakeMiniLMModel{vector: vec},
136
+ normalize: spec.Normalize,
137
+ }, nil
138
+ }
139
+
140
+ engine := NewWithConfig(Config{
141
+ Backend: "bundled",
142
+ RuntimePath: "/opt/onnx/libonnxruntime.so",
143
+ Dimensions: 384,
144
+ Normalize: true,
145
+ })
146
+ if !engine.Ready() {
147
+ t.Fatalf("expected bundled engine to be ready")
148
+ }
149
+ if engine.Mode() != "primary" {
150
+ t.Fatalf("expected primary mode, got %q", engine.Mode())
151
+ }
152
+
153
+ vec, err := engine.EmbedDocument(context.Background(), "hello")
154
+ if err != nil {
155
+ t.Fatalf("EmbedDocument() error = %v", err)
156
+ }
157
+ if len(vec) != 384 {
158
+ t.Fatalf("expected 384 dimensions, got %d", len(vec))
159
+ }
160
+ if vec[0] == 3 || vec[1] == 4 {
161
+ t.Fatalf("expected normalized output from bundled backend")
162
+ }
163
+ }
164
+
165
+ func TestNewWithConfigSupportsPowerUserSeam(t *testing.T) {
166
+ engine := NewWithConfig(Config{
167
+ Backend: "custom-local",
168
+ ModelPath: "/models/custom.onnx",
169
+ Dimensions: 768,
170
+ Normalize: true,
171
+ })
172
+ if !engine.Ready() {
173
+ t.Fatalf("expected custom-local engine to be ready")
174
+ }
175
+ if engine.Mode() != "custom-local" {
176
+ t.Fatalf("expected custom-local mode, got %q", engine.Mode())
177
+ }
178
+ if engine.Dimensions() != 768 {
179
+ t.Fatalf("expected dimensions 768, got %d", engine.Dimensions())
180
+ }
181
+
182
+ fallback := NewWithConfig(Config{
183
+ Backend: "custom-local",
184
+ Dimensions: 512,
185
+ })
186
+ if fallback.Mode() != "fallback" {
187
+ t.Fatalf("expected missing model path to fall back, got %q", fallback.Mode())
188
+ }
189
+ if fallback.Dimensions() != 512 {
190
+ t.Fatalf("expected fallback dimensions 512, got %d", fallback.Dimensions())
191
+ }
192
+
193
+ unavailable := NewWithConfig(Config{
194
+ Backend: "made-up-backend",
195
+ Dimensions: 256,
196
+ })
197
+ if unavailable.Ready() {
198
+ t.Fatalf("expected unsupported backend to be unavailable")
199
+ }
200
+ }
201
+
202
+ func TestEmbedDeterministic384Dimensions(t *testing.T) {
203
+ engine := NewFallback("test fallback")
204
+ first, err := engine.EmbedDocument(context.Background(), "remember the red book")
205
+ if err != nil {
206
+ t.Fatalf("EmbedDocument(first) error = %v", err)
207
+ }
208
+ second, err := engine.EmbedDocument(context.Background(), "remember the red book")
209
+ if err != nil {
210
+ t.Fatalf("EmbedDocument(second) error = %v", err)
211
+ }
212
+ other, err := engine.EmbedDocument(context.Background(), "different text entirely")
213
+ if err != nil {
214
+ t.Fatalf("EmbedDocument(other) error = %v", err)
215
+ }
216
+
217
+ if len(first) != DefaultDimensions {
218
+ t.Fatalf("expected %d dimensions, got %d", DefaultDimensions, len(first))
219
+ }
220
+ if len(second) != DefaultDimensions || len(other) != DefaultDimensions {
221
+ t.Fatalf("expected stable %d-dimensional output", DefaultDimensions)
222
+ }
223
+ for i := range first {
224
+ if first[i] != second[i] {
225
+ t.Fatalf("expected deterministic embedding at index %d", i)
226
+ }
227
+ }
228
+
229
+ var differs bool
230
+ for i := range first {
231
+ if first[i] != other[i] {
232
+ differs = true
233
+ break
234
+ }
235
+ }
236
+ if !differs {
237
+ t.Fatalf("expected different text to change the embedding")
238
+ }
239
+ }
240
+
241
+ func TestBundledFallsBackToConfiguredFallbackProfile(t *testing.T) {
242
+ originalResolver := resolveBundledSpec
243
+ originalFactory := newONNXLocalBackend
244
+ t.Cleanup(func() {
245
+ resolveBundledSpec = originalResolver
246
+ newONNXLocalBackend = originalFactory
247
+ })
248
+
249
+ resolveCalls := make([]string, 0, 2)
250
+ resolveBundledSpec = func(cfg Config) (onnxLocalSpec, error) {
251
+ resolveCalls = append(resolveCalls, cfg.Profile)
252
+ if cfg.Profile == DefaultEmbeddingProfile {
253
+ return onnxLocalSpec{}, errors.New("primary profile unavailable")
254
+ }
255
+ return onnxLocalSpec{
256
+ RuntimePath: cfg.RuntimePath,
257
+ Dimensions: 384,
258
+ Normalize: true,
259
+ Profile: buildProfile(Profile{
260
+ Backend: "bundled",
261
+ Family: FallbackEmbeddingProfile,
262
+ Dimensions: 384,
263
+ Normalize: true,
264
+ }),
265
+ }, nil
266
+ }
267
+ newONNXLocalBackend = func(spec onnxLocalSpec) (embeddingBackend, error) {
268
+ return fakeONNXLocalBackend{vector: make([]float32, spec.Dimensions)}, nil
269
+ }
270
+
271
+ engine := NewWithConfig(Config{
272
+ Backend: "bundled",
273
+ Profile: DefaultEmbeddingProfile,
274
+ FallbackProfile: FallbackEmbeddingProfile,
275
+ RuntimePath: "/opt/onnx/libonnxruntime.so",
276
+ })
277
+ if !engine.Ready() {
278
+ t.Fatalf("expected bundled engine to be ready via fallback profile")
279
+ }
280
+ if engine.Profile().Family != FallbackEmbeddingProfile {
281
+ t.Fatalf("expected fallback family %q, got %q", FallbackEmbeddingProfile, engine.Profile().Family)
282
+ }
283
+ if len(resolveCalls) != 2 || resolveCalls[0] != DefaultEmbeddingProfile || resolveCalls[1] != FallbackEmbeddingProfile {
284
+ t.Fatalf("unexpected resolve order %v", resolveCalls)
285
+ }
286
+ }
287
+
288
+ func TestNomicUsesAsymmetricDocumentAndQueryPrefixes(t *testing.T) {
289
+ originalResolver := resolveBundledSpec
290
+ originalFactory := newONNXLocalBackend
291
+ t.Cleanup(func() {
292
+ resolveBundledSpec = originalResolver
293
+ newONNXLocalBackend = originalFactory
294
+ })
295
+
296
+ resolveBundledSpec = func(cfg Config) (onnxLocalSpec, error) {
297
+ return onnxLocalSpec{
298
+ Family: "nomic-embed-text-v1.5",
299
+ Dimensions: 768,
300
+ Normalize: true,
301
+ Profile: buildProfile(Profile{
302
+ Backend: "onnx-local",
303
+ Family: "nomic-embed-text-v1.5",
304
+ Dimensions: 768,
305
+ Normalize: true,
306
+ }),
307
+ }, nil
308
+ }
309
+
310
+ var calls []string
311
+ newONNXLocalBackend = func(spec onnxLocalSpec) (embeddingBackend, error) {
312
+ return deterministicRecorderBackend{calls: &calls}, nil
313
+ }
314
+
315
+ engine := NewWithConfig(Config{
316
+ Backend: "bundled",
317
+ Profile: "nomic-embed-text-v1.5",
318
+ RuntimePath: "/opt/onnx/libonnxruntime.so",
319
+ })
320
+ if !engine.Ready() {
321
+ t.Fatalf("expected nomic engine to be ready")
322
+ }
323
+
324
+ if _, err := engine.EmbedDocument(context.Background(), "project note"); err != nil {
325
+ t.Fatalf("EmbedDocument() error = %v", err)
326
+ }
327
+ if _, err := engine.EmbedQuery(context.Background(), "project note"); err != nil {
328
+ t.Fatalf("EmbedQuery() error = %v", err)
329
+ }
330
+
331
+ if len(calls) != 3 {
332
+ t.Fatalf("expected 3 backend calls including dimension probe, got %d", len(calls))
333
+ }
334
+ if calls[1] != "search_document: project note" {
335
+ t.Fatalf("unexpected document prefix call %q", calls[1])
336
+ }
337
+ if calls[2] != "search_query: project note" {
338
+ t.Fatalf("unexpected query prefix call %q", calls[2])
339
+ }
340
+ }
341
+
342
+ type deterministicRecorderBackend struct {
343
+ calls *[]string
344
+ }
345
+
346
+ func (b deterministicRecorderBackend) Embed(text string, dimensions int) ([]float32, error) {
347
+ *b.calls = append(*b.calls, text)
348
+ return make([]float32, dimensions), nil
349
+ }
@@ -0,0 +1,93 @@
1
+ package embed
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+ "math"
7
+ "strings"
8
+ )
9
+
10
+ const (
11
+ DimsL1 = 64
12
+ DimsL2 = 256
13
+ DimsL3 = 768
14
+ )
15
+
16
+ type MatryoshkaVec struct {
17
+ L1 []float32
18
+ L2 []float32
19
+ L3 []float32
20
+ }
21
+
22
+ type MatryoshkaEmbedder interface {
23
+ Embedder
24
+ EmbedDocumentM(ctx context.Context, text string) (MatryoshkaVec, error)
25
+ EmbedQueryM(ctx context.Context, text string) (MatryoshkaVec, error)
26
+ }
27
+
28
+ func SupportsMatryoshka(e Embedder) bool {
29
+ if e == nil {
30
+ return false
31
+ }
32
+ return strings.EqualFold(e.Profile().Family, "nomic-embed-text-v1.5")
33
+ }
34
+
35
+ func NewMatryoshkaVec(full []float32) (MatryoshkaVec, error) {
36
+ if len(full) < DimsL3 {
37
+ return MatryoshkaVec{}, fmt.Errorf("matryoshka requires %d-dim vector, got %d", DimsL3, len(full))
38
+ }
39
+ return MatryoshkaVec{
40
+ L1: truncateAndNormalize(full, DimsL1),
41
+ L2: truncateAndNormalize(full, DimsL2),
42
+ L3: truncateAndNormalize(full, DimsL3),
43
+ }, nil
44
+ }
45
+
46
+ func truncateAndNormalize(v []float32, dims int) []float32 {
47
+ if dims <= 0 {
48
+ return nil
49
+ }
50
+ if dims > len(v) {
51
+ dims = len(v)
52
+ }
53
+
54
+ t := make([]float32, dims)
55
+ copy(t, v[:dims])
56
+
57
+ var norm float64
58
+ for _, x := range t {
59
+ norm += float64(x) * float64(x)
60
+ }
61
+ norm = math.Sqrt(norm)
62
+ if norm < 1e-9 {
63
+ return t
64
+ }
65
+
66
+ scale := float32(norm)
67
+ for i := range t {
68
+ t[i] /= scale
69
+ }
70
+ return t
71
+ }
72
+
73
+ func (e *Engine) EmbedDocumentM(ctx context.Context, text string) (MatryoshkaVec, error) {
74
+ if !SupportsMatryoshka(e) {
75
+ return MatryoshkaVec{}, fmt.Errorf("matryoshka unavailable for profile family %q", e.Profile().Family)
76
+ }
77
+ full, err := e.EmbedDocument(ctx, text)
78
+ if err != nil {
79
+ return MatryoshkaVec{}, err
80
+ }
81
+ return NewMatryoshkaVec(full)
82
+ }
83
+
84
+ func (e *Engine) EmbedQueryM(ctx context.Context, text string) (MatryoshkaVec, error) {
85
+ if !SupportsMatryoshka(e) {
86
+ return MatryoshkaVec{}, fmt.Errorf("matryoshka unavailable for profile family %q", e.Profile().Family)
87
+ }
88
+ full, err := e.EmbedQuery(ctx, text)
89
+ if err != nil {
90
+ return MatryoshkaVec{}, err
91
+ }
92
+ return NewMatryoshkaVec(full)
93
+ }
@@ -0,0 +1,150 @@
1
+ package embed
2
+
3
+ import (
4
+ "context"
5
+ "math"
6
+ "math/rand"
7
+ "os"
8
+ "strings"
9
+ "testing"
10
+ )
11
+
12
+ func TestTruncateAndNormalize(t *testing.T) {
13
+ full := randomUnitVec(DimsL3)
14
+ mv, err := NewMatryoshkaVec(full)
15
+ if err != nil {
16
+ t.Fatal(err)
17
+ }
18
+
19
+ for _, tc := range []struct {
20
+ name string
21
+ v []float32
22
+ }{
23
+ {"L1", mv.L1},
24
+ {"L2", mv.L2},
25
+ {"L3", mv.L3},
26
+ } {
27
+ var norm float64
28
+ for _, x := range tc.v {
29
+ norm += float64(x) * float64(x)
30
+ }
31
+ norm = math.Sqrt(norm)
32
+ if math.Abs(norm-1.0) > 1e-5 {
33
+ t.Errorf("%s: L2 norm = %.8f, want 1.0", tc.name, norm)
34
+ }
35
+ }
36
+ }
37
+
38
+ func TestMatryoshkaSimilarityPreservation(t *testing.T) {
39
+ if testing.Short() {
40
+ t.Skip("requires real Nomic model")
41
+ }
42
+
43
+ engine := loadNomicEngine(t)
44
+ ctx := context.Background()
45
+
46
+ pairs := []struct {
47
+ doc string
48
+ query string
49
+ }{
50
+ {
51
+ doc: "The eviction formula uses logarithmic frequency damping.",
52
+ query: "how does the system decide which model to remove",
53
+ },
54
+ {
55
+ doc: "Compaction clusters session turns into summarized records.",
56
+ query: "memory cleanup after a conversation ends",
57
+ },
58
+ {
59
+ doc: "Nomic embeddings support Matryoshka representation learning.",
60
+ query: "truncated vectors for fast approximate search",
61
+ },
62
+ }
63
+
64
+ tiers := []struct {
65
+ label string
66
+ threshold float64
67
+ selectVec func(MatryoshkaVec) []float32
68
+ }{
69
+ {
70
+ label: "L2 (256d)",
71
+ threshold: 0.90,
72
+ selectVec: func(v MatryoshkaVec) []float32 { return v.L2 },
73
+ },
74
+ {
75
+ label: "L1 (64d)",
76
+ threshold: 0.70,
77
+ selectVec: func(v MatryoshkaVec) []float32 { return v.L1 },
78
+ },
79
+ }
80
+
81
+ for _, pair := range pairs {
82
+ dFull, err := engine.EmbedDocumentM(ctx, pair.doc)
83
+ if err != nil {
84
+ t.Fatalf("EmbedDocumentM() error = %v", err)
85
+ }
86
+ qFull, err := engine.EmbedQueryM(ctx, pair.query)
87
+ if err != nil {
88
+ t.Fatalf("EmbedQueryM() error = %v", err)
89
+ }
90
+
91
+ simFull := cosineEval(dFull.L3, qFull.L3)
92
+ for _, tier := range tiers {
93
+ simTier := cosineEval(tier.selectVec(dFull), tier.selectVec(qFull))
94
+ if math.Abs(simFull) < 0.05 {
95
+ continue
96
+ }
97
+
98
+ ratio := simTier / simFull
99
+ if ratio < tier.threshold {
100
+ label := pair.doc
101
+ if len(label) > 40 {
102
+ label = label[:40]
103
+ }
104
+ t.Errorf("%s preservation below threshold for pair %q: full=%.4f tier=%.4f ratio=%.4f threshold=%.2f",
105
+ tier.label, label, simFull, simTier, ratio, tier.threshold)
106
+ }
107
+ }
108
+ }
109
+ }
110
+
111
+ func randomUnitVec(dims int) []float32 {
112
+ rng := rand.New(rand.NewSource(42))
113
+ vec := make([]float32, dims)
114
+ var norm float64
115
+ for i := range vec {
116
+ value := rng.NormFloat64()
117
+ vec[i] = float32(value)
118
+ norm += value * value
119
+ }
120
+ norm = math.Sqrt(norm)
121
+ scale := float32(norm)
122
+ for i := range vec {
123
+ vec[i] /= scale
124
+ }
125
+ return vec
126
+ }
127
+
128
+ func loadNomicEngine(t *testing.T) *Engine {
129
+ t.Helper()
130
+
131
+ runtimePath := strings.TrimSpace(os.Getenv("LIBRAVDB_EVAL_ONNX_RUNTIME"))
132
+ modelDir := strings.TrimSpace(os.Getenv("LIBRAVDB_EVAL_NOMIC_DIR"))
133
+ if runtimePath == "" || modelDir == "" {
134
+ t.Skip("set LIBRAVDB_EVAL_ONNX_RUNTIME and LIBRAVDB_EVAL_NOMIC_DIR to run real Nomic Matryoshka tests")
135
+ }
136
+
137
+ engine := NewWithConfig(Config{
138
+ Backend: "onnx-local",
139
+ Profile: "nomic-embed-text-v1.5",
140
+ RuntimePath: runtimePath,
141
+ ModelPath: modelDir,
142
+ })
143
+ if !engine.Ready() {
144
+ t.Fatalf("engine not ready: %s", engine.Reason())
145
+ }
146
+ if !SupportsMatryoshka(engine) {
147
+ t.Fatalf("expected Nomic engine to support Matryoshka")
148
+ }
149
+ return engine
150
+ }