Files
Maven/internal/memory/store.go
T
kami 880715fad4 maven: long-term memory vector-store interface (task 7)
- New internal/memory/ package: Store interface, InMemoryStore (cosine sim)
- Tests: insert→search roundtrip, topK truncation, empty store, cosine edges
- Wire into voice: memStore on reactiveHandler, insert note embedding after WriteNote
- Memory Insert is best-effort, log-and-continue on error

Co-Authored-By: opencode <opencode@anthropic.com>
2026-07-06 04:18:50 +04:00

96 lines
1.9 KiB
Go

package memory
import (
"context"
"sort"
"sync"
)
// Result is a single search hit.
type Result struct {
ID string
Score float64
Meta map[string]string
}
// Store is a vector memory interface.
type Store interface {
Insert(ctx context.Context, id string, vec []float32, meta map[string]string) error
Search(ctx context.Context, vec []float32, topK int) ([]Result, error)
}
// item is a single stored vector with metadata.
type item struct {
id string
vec []float32
meta map[string]string
}
// InMemoryStore implements Store with cosine similarity search.
type InMemoryStore struct {
mu sync.RWMutex
items []item
}
func NewInMemoryStore() *InMemoryStore {
return &InMemoryStore{}
}
func (s *InMemoryStore) Insert(_ context.Context, id string, vec []float32, meta map[string]string) error {
s.mu.Lock()
s.items = append(s.items, item{id: id, vec: vec, meta: meta})
s.mu.Unlock()
return nil
}
func (s *InMemoryStore) Search(_ context.Context, vec []float32, topK int) ([]Result, error) {
s.mu.RLock()
defer s.mu.RUnlock()
if topK <= 0 {
topK = 10
}
type scored struct {
id string
score float64
meta map[string]string
}
scores := make([]scored, 0, len(s.items))
for _, it := range s.items {
score := cosine(vec, it.vec)
scores = append(scores, scored{id: it.id, score: score, meta: it.meta})
}
sort.Slice(scores, func(i, j int) bool {
return scores[i].score > scores[j].score // descending
})
if topK > len(scores) {
topK = len(scores)
}
out := make([]Result, topK)
for i := 0; i < topK; i++ {
out[i] = Result{
ID: scores[i].id,
Score: scores[i].score,
Meta: scores[i].meta,
}
}
return out, nil
}
// cosine similarity (dot product, assumes L2-normalized vectors).
func cosine(a, b []float32) float64 {
if len(a) != len(b) || len(a) == 0 {
return 0
}
var dot float64
for i := range a {
dot += float64(a[i]) * float64(b[i])
}
return dot
}