@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,696 @@
1
+ package embed
2
+
3
+ import (
4
+ "context"
5
+ "crypto/sha256"
6
+ "errors"
7
+ "fmt"
8
+ "math"
9
+ "os"
10
+ "path/filepath"
11
+ "runtime"
12
+ "strings"
13
+
14
+ "github.com/sugarme/tokenizer"
15
+ )
16
+
17
+ const (
18
+ DefaultBackend = "bundled"
19
+ DefaultDimensions = 768
20
+ longDocWindowSize = 512
21
+ longDocStride = 256
22
+ )
23
+
24
+ type Config struct {
25
+ Backend string
26
+ Profile string
27
+ FallbackProfile string
28
+ RuntimePath string
29
+ ModelPath string
30
+ TokenizerPath string
31
+ Dimensions int
32
+ Normalize bool
33
+ }
34
+
35
+ type Embedder interface {
36
+ EmbedDocument(ctx context.Context, text string) ([]float32, error)
37
+ EmbedQuery(ctx context.Context, text string) ([]float32, error)
38
+ Dimensions() int
39
+ Profile() Profile
40
+ Ready() bool
41
+ Reason() string
42
+ Mode() string
43
+ }
44
+
45
+ type Engine struct {
46
+ dimensions int
47
+ ready bool
48
+ reason string
49
+ mode string
50
+ backend embeddingBackend
51
+ profile Profile
52
+ }
53
+
54
+ type embeddingBackend interface {
55
+ Embed(text string, dimensions int) ([]float32, error)
56
+ }
57
+
58
+ type deterministicBackend struct {
59
+ normalize bool
60
+ }
61
+
62
+ type miniLMModel interface {
63
+ Compute(sentence string, addSpecialTokens bool) ([]float32, error)
64
+ TokenCount(sentence string, addSpecialTokens bool) (int, error)
65
+ Encode(sentence string, addSpecialTokens bool) (*tokenizer.Encoding, error)
66
+ ComputeEncoding(encoding tokenizer.Encoding) ([]float32, error)
67
+ }
68
+
69
+ type miniLMBackend struct {
70
+ model miniLMModel
71
+ normalize bool
72
+ }
73
+
74
+ var resolveBundledModelDir = func(profile string) (string, error) {
75
+ profile = strings.TrimSpace(profile)
76
+ if profile == "" {
77
+ profile = DefaultEmbeddingProfile
78
+ }
79
+
80
+ candidates := make([]string, 0, 16)
81
+ if exe, err := os.Executable(); err == nil && strings.TrimSpace(exe) != "" {
82
+ exeDir := filepath.Dir(exe)
83
+ candidates = append(candidates,
84
+ filepath.Join(exeDir, "models", profile),
85
+ filepath.Join(exeDir, "..", ".models", profile),
86
+ filepath.Join(exeDir, "..", "models", profile),
87
+ )
88
+ candidates = append(candidates, ancestorModelDirCandidates(exeDir, profile)...)
89
+ }
90
+ if cwd, err := os.Getwd(); err == nil && strings.TrimSpace(cwd) != "" {
91
+ candidates = append(candidates,
92
+ filepath.Join(cwd, "..", ".models", profile),
93
+ filepath.Join(cwd, ".models", profile),
94
+ filepath.Join(cwd, "models", profile),
95
+ )
96
+ candidates = append(candidates, ancestorModelDirCandidates(cwd, profile)...)
97
+ }
98
+
99
+ seen := map[string]struct{}{}
100
+ for _, candidate := range candidates {
101
+ candidate = filepath.Clean(candidate)
102
+ if _, ok := seen[candidate]; ok {
103
+ continue
104
+ }
105
+ seen[candidate] = struct{}{}
106
+ if info, err := os.Stat(candidate); err == nil && info.IsDir() {
107
+ manifestPath := filepath.Join(candidate, defaultManifestName)
108
+ if _, err := os.Stat(manifestPath); err == nil {
109
+ return candidate, nil
110
+ }
111
+ }
112
+ }
113
+
114
+ return "", fmt.Errorf("bundled profile %q assets not found; expected embedding.json under a shipped model directory", profile)
115
+ }
116
+
117
+ var resolveBundledSpec = func(cfg Config) (onnxLocalSpec, error) {
118
+ profileName := strings.TrimSpace(cfg.Profile)
119
+ if profileName == "" {
120
+ profileName = DefaultEmbeddingProfile
121
+ }
122
+ modelDir := strings.TrimSpace(cfg.ModelPath)
123
+ if modelDir == "" {
124
+ var err error
125
+ modelDir, err = resolveBundledModelDir(profileName)
126
+ if err != nil {
127
+ return onnxLocalSpec{}, err
128
+ }
129
+ }
130
+
131
+ runtimePath := strings.TrimSpace(cfg.RuntimePath)
132
+ if runtimePath == "" {
133
+ var err error
134
+ runtimePath, err = resolveBundledRuntimePath()
135
+ if err != nil {
136
+ return onnxLocalSpec{}, err
137
+ }
138
+ }
139
+
140
+ return resolveONNXLocalSpec(Config{
141
+ Backend: "onnx-local",
142
+ Profile: profileName,
143
+ RuntimePath: runtimePath,
144
+ ModelPath: modelDir,
145
+ TokenizerPath: cfg.TokenizerPath,
146
+ Dimensions: cfg.Dimensions,
147
+ Normalize: cfg.Normalize,
148
+ })
149
+ }
150
+
151
+ var resolveBundledRuntimePath = func() (string, error) {
152
+ libName := bundledRuntimeLibName()
153
+ if libName == "" {
154
+ return "", fmt.Errorf("unsupported platform for bundled onnx runtime: %s/%s", runtime.GOOS, runtime.GOARCH)
155
+ }
156
+
157
+ candidates := make([]string, 0, 20)
158
+ if exe, err := os.Executable(); err == nil && strings.TrimSpace(exe) != "" {
159
+ exeDir := filepath.Dir(exe)
160
+ candidates = append(candidates,
161
+ filepath.Join(exeDir, "onnxruntime", "lib", libName),
162
+ filepath.Join(exeDir, "..", ".models", "onnxruntime", "*", "lib", libName),
163
+ filepath.Join(exeDir, "..", "models", "onnxruntime", "*", "lib", libName),
164
+ )
165
+ candidates = append(candidates, ancestorRuntimeCandidates(exeDir, libName)...)
166
+ }
167
+ if cwd, err := os.Getwd(); err == nil && strings.TrimSpace(cwd) != "" {
168
+ candidates = append(candidates,
169
+ filepath.Join(cwd, "..", ".models", "onnxruntime", "*", "lib", libName),
170
+ filepath.Join(cwd, ".models", "onnxruntime", "*", "lib", libName),
171
+ filepath.Join(cwd, "models", "onnxruntime", "*", "lib", libName),
172
+ )
173
+ candidates = append(candidates, ancestorRuntimeCandidates(cwd, libName)...)
174
+ }
175
+
176
+ seen := map[string]struct{}{}
177
+ for _, pattern := range candidates {
178
+ matches := []string{pattern}
179
+ if strings.Contains(pattern, "*") {
180
+ globbed, _ := filepath.Glob(pattern)
181
+ matches = globbed
182
+ }
183
+ for _, match := range matches {
184
+ match = filepath.Clean(match)
185
+ if _, ok := seen[match]; ok {
186
+ continue
187
+ }
188
+ seen[match] = struct{}{}
189
+ if info, err := os.Stat(match); err == nil && !info.IsDir() {
190
+ return match, nil
191
+ }
192
+ }
193
+ }
194
+
195
+ return "", fmt.Errorf("bundled onnx runtime library %q not found in shipped asset locations", libName)
196
+ }
197
+
198
+ func bundledRuntimeLibName() string {
199
+ switch runtime.GOOS {
200
+ case "darwin":
201
+ return "libonnxruntime.dylib"
202
+ case "linux":
203
+ return "libonnxruntime.so"
204
+ case "windows":
205
+ return "onnxruntime.dll"
206
+ default:
207
+ return ""
208
+ }
209
+ }
210
+
211
+ func ResolveRuntimePath(cfg Config) (string, error) {
212
+ runtimePath := strings.TrimSpace(cfg.RuntimePath)
213
+ if runtimePath != "" {
214
+ return runtimePath, nil
215
+ }
216
+
217
+ switch strings.TrimSpace(cfg.Backend) {
218
+ case "", "bundled":
219
+ return resolveBundledRuntimePath()
220
+ case "onnx-local":
221
+ return "", fmt.Errorf("onnx-local embedder requires runtime path or unpacked bundled runtime")
222
+ default:
223
+ return "", fmt.Errorf("backend %q does not use ONNX runtime resolution", cfg.Backend)
224
+ }
225
+ }
226
+
227
+ func ancestorModelDirCandidates(start, profile string) []string {
228
+ return walkAncestors(start, func(dir string) []string {
229
+ return []string{
230
+ filepath.Join(dir, ".models", profile),
231
+ filepath.Join(dir, "models", profile),
232
+ }
233
+ })
234
+ }
235
+
236
+ func ancestorRuntimeCandidates(start, libName string) []string {
237
+ return walkAncestors(start, func(dir string) []string {
238
+ return []string{
239
+ filepath.Join(dir, ".models", "onnxruntime", "*", "lib", libName),
240
+ filepath.Join(dir, "models", "onnxruntime", "*", "lib", libName),
241
+ }
242
+ })
243
+ }
244
+
245
+ func walkAncestors(start string, build func(string) []string) []string {
246
+ start = filepath.Clean(start)
247
+ var results []string
248
+ dir := start
249
+ for {
250
+ results = append(results, build(dir)...)
251
+ parent := filepath.Dir(dir)
252
+ if parent == dir {
253
+ break
254
+ }
255
+ dir = parent
256
+ }
257
+ return results
258
+ }
259
+
260
+ func New(dimensions int) *Engine {
261
+ return &Engine{dimensions: dimensions}
262
+ }
263
+
264
+ func (e *Engine) Dimensions() int {
265
+ return e.dimensions
266
+ }
267
+
268
+ func (e *Engine) Ready() bool {
269
+ return e.ready
270
+ }
271
+
272
+ func (e *Engine) Reason() string {
273
+ return e.reason
274
+ }
275
+
276
+ func (e *Engine) Mode() string {
277
+ return e.mode
278
+ }
279
+
280
+ func (e *Engine) Profile() Profile {
281
+ return e.profile
282
+ }
283
+
284
+ func NewUnavailable(reason string) *Engine {
285
+ return &Engine{
286
+ dimensions: DefaultDimensions,
287
+ ready: false,
288
+ reason: reason,
289
+ mode: "unavailable",
290
+ }
291
+ }
292
+
293
+ func NewPrimary(runtimePath string) *Engine {
294
+ return NewWithConfig(Config{
295
+ Backend: DefaultBackend,
296
+ Profile: DefaultEmbeddingProfile,
297
+ FallbackProfile: FallbackEmbeddingProfile,
298
+ RuntimePath: runtimePath,
299
+ Dimensions: DefaultDimensions,
300
+ Normalize: true,
301
+ })
302
+ }
303
+
304
+ func NewWithConfig(cfg Config) *Engine {
305
+ cfg = normalizeConfig(cfg)
306
+
307
+ switch cfg.Backend {
308
+ case "bundled":
309
+ engine, err := newBundledEngine(cfg)
310
+ if err != nil {
311
+ return NewFallbackWithConfig(cfg, fmt.Sprintf("bundled embedder unavailable (%v); using deterministic local fallback", err))
312
+ }
313
+ return engine
314
+ case "onnx-local":
315
+ if cfg.RuntimePath == "" || cfg.ModelPath == "" {
316
+ return NewUnavailable("onnx-local requires ONNX runtime path and embedding model directory/manifest")
317
+ }
318
+ spec, err := resolveONNXLocalSpec(cfg)
319
+ if err != nil {
320
+ return NewUnavailable(err.Error())
321
+ }
322
+ backend, err := newONNXLocalBackend(spec)
323
+ if err != nil {
324
+ return NewUnavailable(err.Error())
325
+ }
326
+ engine := &Engine{
327
+ dimensions: spec.Dimensions,
328
+ ready: true,
329
+ reason: "",
330
+ mode: "onnx-local",
331
+ backend: backend,
332
+ profile: spec.Profile,
333
+ }
334
+ if err := verifyDimensions(engine); err != nil {
335
+ return NewUnavailable(err.Error())
336
+ }
337
+ return engine
338
+ case "custom-local":
339
+ if cfg.ModelPath == "" {
340
+ return NewFallbackWithConfig(cfg, "missing custom embedding model path; using deterministic local fallback")
341
+ }
342
+ engine := &Engine{
343
+ dimensions: cfg.Dimensions,
344
+ ready: true,
345
+ reason: "",
346
+ mode: "custom-local",
347
+ backend: deterministicBackend{normalize: cfg.Normalize},
348
+ profile: buildProfile(Profile{
349
+ Backend: "custom-local",
350
+ Family: "custom-local",
351
+ Dimensions: cfg.Dimensions,
352
+ Normalize: cfg.Normalize,
353
+ MaxContextTokens: 0,
354
+ ModelPath: cfg.ModelPath,
355
+ Tokenizer: cfg.TokenizerPath,
356
+ }),
357
+ }
358
+ if err := verifyDimensions(engine); err != nil {
359
+ return NewUnavailable(err.Error())
360
+ }
361
+ return engine
362
+ default:
363
+ return NewUnavailable(fmt.Sprintf("unsupported embedding backend: %s", cfg.Backend))
364
+ }
365
+ }
366
+
367
+ func normalizeConfig(cfg Config) Config {
368
+ cfg.Backend = strings.TrimSpace(cfg.Backend)
369
+ if cfg.Backend == "" {
370
+ cfg.Backend = DefaultBackend
371
+ }
372
+ cfg.Profile = strings.TrimSpace(cfg.Profile)
373
+ cfg.FallbackProfile = strings.TrimSpace(cfg.FallbackProfile)
374
+ if cfg.Profile == "" && cfg.Backend == "bundled" {
375
+ cfg.Profile = DefaultEmbeddingProfile
376
+ }
377
+ if cfg.FallbackProfile == "" && cfg.Backend == "bundled" {
378
+ cfg.FallbackProfile = FallbackEmbeddingProfile
379
+ }
380
+ if cfg.Dimensions <= 0 && cfg.Backend != "onnx-local" {
381
+ if profile, ok := lookupProfile(cfg.Profile); ok {
382
+ cfg.Dimensions = profile.Dimensions
383
+ cfg.Normalize = profile.Normalize
384
+ } else {
385
+ cfg.Dimensions = DefaultDimensions
386
+ }
387
+ }
388
+ return cfg
389
+ }
390
+
391
+ func newBundledEngine(cfg Config) (*Engine, error) {
392
+ profiles := []string{cfg.Profile}
393
+ if cfg.ModelPath == "" && cfg.FallbackProfile != "" && cfg.FallbackProfile != cfg.Profile {
394
+ profiles = append(profiles, cfg.FallbackProfile)
395
+ }
396
+
397
+ var failures []string
398
+ for i, profile := range profiles {
399
+ candidate := cfg
400
+ candidate.Profile = profile
401
+ if i > 0 {
402
+ candidate.ModelPath = ""
403
+ candidate.TokenizerPath = ""
404
+ candidate.Dimensions = 0
405
+ }
406
+
407
+ spec, err := resolveBundledSpec(candidate)
408
+ if err != nil {
409
+ failures = append(failures, fmt.Sprintf("%s: %v", profile, err))
410
+ continue
411
+ }
412
+ backend, err := newONNXLocalBackend(spec)
413
+ if err != nil {
414
+ failures = append(failures, fmt.Sprintf("%s: %v", profile, err))
415
+ continue
416
+ }
417
+ engine := &Engine{
418
+ dimensions: spec.Dimensions,
419
+ ready: true,
420
+ reason: "",
421
+ mode: "primary",
422
+ backend: backend,
423
+ profile: spec.Profile,
424
+ }
425
+ if err := verifyDimensions(engine); err != nil {
426
+ failures = append(failures, fmt.Sprintf("%s: %v", profile, err))
427
+ continue
428
+ }
429
+ return engine, nil
430
+ }
431
+
432
+ return nil, errors.New(strings.Join(failures, "; "))
433
+ }
434
+
435
+ func NewFallback(reason string) *Engine {
436
+ return NewFallbackWithConfig(Config{
437
+ Backend: DefaultBackend,
438
+ Dimensions: DefaultDimensions,
439
+ Normalize: true,
440
+ }, reason)
441
+ }
442
+
443
+ func NewFallbackWithConfig(cfg Config, reason string) *Engine {
444
+ cfg = normalizeConfig(cfg)
445
+ engine := &Engine{
446
+ dimensions: cfg.Dimensions,
447
+ ready: true,
448
+ reason: reason,
449
+ mode: "fallback",
450
+ backend: deterministicBackend{normalize: cfg.Normalize},
451
+ profile: buildProfile(Profile{
452
+ Backend: "fallback",
453
+ Family: "deterministic-local",
454
+ Dimensions: cfg.Dimensions,
455
+ Normalize: cfg.Normalize,
456
+ MaxContextTokens: 0,
457
+ }),
458
+ }
459
+ if err := verifyDimensions(engine); err != nil {
460
+ return NewUnavailable(err.Error())
461
+ }
462
+ return engine
463
+ }
464
+
465
+ func verifyDimensions(e *Engine) error {
466
+ vec, err := e.EmbedDocument(context.Background(), "dimension probe")
467
+ if err != nil {
468
+ return fmt.Errorf("dimension verification failed: %w", err)
469
+ }
470
+ if got := len(vec); got != e.dimensions {
471
+ return fmt.Errorf("dimension verification failed: got %d, want %d", got, e.dimensions)
472
+ }
473
+ return nil
474
+ }
475
+
476
+ func (e *Engine) EmbedDocument(_ context.Context, text string) ([]float32, error) {
477
+ prefixed := e.documentPrefix() + text
478
+ if vec, ok, err := e.embedLongDocument(prefixed); ok {
479
+ return vec, err
480
+ }
481
+ return e.embed(prefixed)
482
+ }
483
+
484
+ func (e *Engine) EmbedQuery(_ context.Context, text string) ([]float32, error) {
485
+ return e.embed(e.queryPrefix() + text)
486
+ }
487
+
488
+ func (e *Engine) documentPrefix() string {
489
+ switch e.profile.Family {
490
+ case "nomic-embed-text-v1.5":
491
+ return "search_document: "
492
+ default:
493
+ return ""
494
+ }
495
+ }
496
+
497
+ func (e *Engine) queryPrefix() string {
498
+ switch e.profile.Family {
499
+ case "nomic-embed-text-v1.5":
500
+ return "search_query: "
501
+ default:
502
+ return ""
503
+ }
504
+ }
505
+
506
+ func (e *Engine) embed(text string) ([]float32, error) {
507
+ if !e.ready {
508
+ return nil, fmt.Errorf("embedding engine not ready: %s", e.reason)
509
+ }
510
+
511
+ if e.backend == nil {
512
+ return nil, errors.New("embedding backend not configured")
513
+ }
514
+ return e.backend.Embed(text, e.dimensions)
515
+ }
516
+
517
+ func (e *Engine) TokenCountDocument(_ context.Context, text string) (int, error) {
518
+ return e.tokenCount(e.documentPrefix() + text)
519
+ }
520
+
521
+ func (e *Engine) TokenCountQuery(_ context.Context, text string) (int, error) {
522
+ return e.tokenCount(e.queryPrefix() + text)
523
+ }
524
+
525
+ func (e *Engine) tokenCount(text string) (int, error) {
526
+ if !e.ready {
527
+ return 0, fmt.Errorf("embedding engine not ready: %s", e.reason)
528
+ }
529
+
530
+ counter, ok := e.backend.(interface {
531
+ TokenCount(text string) (int, error)
532
+ })
533
+ if !ok {
534
+ return 0, fmt.Errorf("token count unavailable for backend family %q", e.profile.Family)
535
+ }
536
+ return counter.TokenCount(text)
537
+ }
538
+
539
+ func (e *Engine) embedLongDocument(text string) ([]float32, bool, error) {
540
+ if !strings.EqualFold(e.profile.Family, "nomic-embed-text-v1.5") {
541
+ return nil, false, nil
542
+ }
543
+
544
+ tokenAware, ok := e.backend.(interface {
545
+ Encode(text string) (*tokenizer.Encoding, error)
546
+ EmbedEncoding(encoding tokenizer.Encoding, dimensions int) ([]float32, error)
547
+ })
548
+ if !ok {
549
+ return nil, false, nil
550
+ }
551
+
552
+ encoding, err := tokenAware.Encode(text)
553
+ if err != nil {
554
+ return nil, true, err
555
+ }
556
+
557
+ windowSize := longDocWindowSize
558
+ if e.profile.MaxContextTokens > 0 && windowSize > e.profile.MaxContextTokens {
559
+ windowSize = e.profile.MaxContextTokens
560
+ }
561
+ if windowSize <= 0 {
562
+ return nil, false, nil
563
+ }
564
+ if len(encoding.Ids) <= windowSize {
565
+ vec, err := tokenAware.EmbedEncoding(*encoding, e.dimensions)
566
+ return vec, true, err
567
+ }
568
+
569
+ stride := longDocStride
570
+ if stride <= 0 || stride >= windowSize {
571
+ stride = windowSize / 2
572
+ }
573
+ if stride <= 0 {
574
+ stride = 1
575
+ }
576
+
577
+ windowed := encoding.Clone()
578
+ if _, err := windowed.Truncate(windowSize, stride); err != nil {
579
+ return nil, true, err
580
+ }
581
+
582
+ windows := make([]tokenizer.Encoding, 0, 1+len(windowed.Overflowing))
583
+ windows = append(windows, *windowed)
584
+ windows = append(windows, windowed.Overflowing...)
585
+
586
+ vecs := make([][]float32, 0, len(windows))
587
+ for _, window := range windows {
588
+ vec, err := tokenAware.EmbedEncoding(window, e.dimensions)
589
+ if err != nil {
590
+ return nil, true, err
591
+ }
592
+ vecs = append(vecs, vec)
593
+ }
594
+
595
+ return meanPoolVectors(vecs), true, nil
596
+ }
597
+
598
+ func (b deterministicBackend) Embed(text string, dimensions int) ([]float32, error) {
599
+ vec := deterministicEmbedding(text, dimensions)
600
+ if b.normalize {
601
+ normalizeEmbedding(vec)
602
+ }
603
+ return vec, nil
604
+ }
605
+
606
+ func (b miniLMBackend) Embed(text string, dimensions int) ([]float32, error) {
607
+ vec, err := b.model.Compute(text, true)
608
+ if err != nil {
609
+ return nil, err
610
+ }
611
+ if len(vec) != dimensions {
612
+ return nil, fmt.Errorf("unexpected embedding dimensions: got %d, want %d", len(vec), dimensions)
613
+ }
614
+ if b.normalize {
615
+ normalizeEmbedding(vec)
616
+ }
617
+ return vec, nil
618
+ }
619
+
620
+ func (b miniLMBackend) TokenCount(text string) (int, error) {
621
+ return b.model.TokenCount(text, true)
622
+ }
623
+
624
+ func (b miniLMBackend) Encode(text string) (*tokenizer.Encoding, error) {
625
+ return b.model.Encode(text, true)
626
+ }
627
+
628
+ func (b miniLMBackend) EmbedEncoding(encoding tokenizer.Encoding, dimensions int) ([]float32, error) {
629
+ vec, err := b.model.ComputeEncoding(encoding)
630
+ if err != nil {
631
+ return nil, err
632
+ }
633
+ if len(vec) != dimensions {
634
+ return nil, fmt.Errorf("unexpected embedding dimensions: got %d, want %d", len(vec), dimensions)
635
+ }
636
+ if b.normalize {
637
+ normalizeEmbedding(vec)
638
+ }
639
+ return vec, nil
640
+ }
641
+
642
+ func deterministicEmbedding(text string, dimensions int) []float32 {
643
+ vec := make([]float32, dimensions)
644
+ if dimensions <= 0 {
645
+ return vec
646
+ }
647
+
648
+ tokens := strings.Fields(strings.ToLower(text))
649
+ if len(tokens) == 0 {
650
+ tokens = []string{text}
651
+ }
652
+
653
+ for _, token := range tokens {
654
+ sum := sha256.Sum256([]byte(token))
655
+ for i := 0; i < dimensions; i++ {
656
+ b := sum[i%len(sum)]
657
+ value := (float32(b) / 127.5) - 1.0
658
+ vec[i] += value
659
+ }
660
+ }
661
+
662
+ normalizeEmbedding(vec)
663
+ return vec
664
+ }
665
+
666
+ func normalizeEmbedding(vec []float32) {
667
+ var norm float64
668
+ for _, v := range vec {
669
+ norm += float64(v * v)
670
+ }
671
+ if norm == 0 {
672
+ return
673
+ }
674
+
675
+ scale := float32(1.0 / math.Sqrt(norm))
676
+ for i := range vec {
677
+ vec[i] *= scale
678
+ }
679
+ }
680
+
681
+ func meanPoolVectors(vecs [][]float32) []float32 {
682
+ if len(vecs) == 0 {
683
+ return nil
684
+ }
685
+ result := make([]float32, len(vecs[0]))
686
+ for _, vec := range vecs {
687
+ for i, value := range vec {
688
+ result[i] += value
689
+ }
690
+ }
691
+ denom := float32(len(vecs))
692
+ for i := range result {
693
+ result[i] /= denom
694
+ }
695
+ return result
696
+ }