Ctrl K
ComunidadGuías…

internal/memory/record_index_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package memory_test

import (
	"context"
	"sync"
	"testing"

	"github.com/yeomyeonggeori/blueclaw/internal/memory"
	"github.com/yeomyeonggeori/blueclaw/internal/memory/memorytest"
	"github.com/yeomyeonggeori/bluememo/bluememotest"
)

const (
	frameText     = `Data room document ""`
	leaseText     = `Data room document "본사 사무실 임대차계약": 월세 800만원`
	auditText     = `Data room document "2025 감사보고서": 적정 의견`
	otherText     = `Some other source's fact about rent`
	rentQuestion  = "사무실 월세 얼마 내?"
	documentsName = "company_document_recall"
)

type tallyingTableEmbedder struct {
	bluememotest.TableEmbedder
	mutex    sync.Mutex
	embedded map[string]int
}

func (embedder *tallyingTableEmbedder) EmbedDocuments(ctx context.Context, texts []string) ([][]float32, error) {
	embedder.mutex.Lock()
	for _, text := range texts {
		embedder.embedded[text]++
	}
	embedder.mutex.Unlock()
	return embedder.TableEmbedder.EmbedDocuments(ctx, texts)
}

func rentEmbedder() *tallyingTableEmbedder {
	return &tallyingTableEmbedder{embedded: map[string]int{}, TableEmbedder: bluememotest.TableEmbedder{Vectors: map[string][]float32{
		frameText:    bluememotest.Axes(map[int]float32{0: 1}),
		leaseText:    bluememotest.Axes(map[int]float32{0: 1, 1: 1}),
		auditText:    bluememotest.Axes(map[int]float32{0: 1, 2: 1}),
		otherText:    bluememotest.Axes(map[int]float32{1: 1}),
		rentQuestion: bluememotest.Axes(map[int]float32{0: 1, 1: 1}),
	}}}
}

func holdRentRecords(t *testing.T, index *memory.RecordIndex) {
	t.Helper()
	ctx := context.Background()
	if errorValue := index.HoldFrame(ctx, documentsName, frameText); errorValue != nil {
		t.Fatalf("hold frame: %v", errorValue)
	}
	for _, entry := range []memory.RecordEntry{
		{ID: "lease", Content: leaseText, ChangedAt: "2026-10-01T00:00:00Z"},
		{ID: "audit", Content: auditText, ChangedAt: "2026-10-01T00:00:00Z"},
	} {
		if errorValue := index.Hold(ctx, documentsName, entry); errorValue != nil {
			t.Fatalf("hold %s: %v", entry.ID, errorValue)
		}
	}
	if errorValue := index.Hold(ctx, "another_recall", memory.RecordEntry{ID: "rent", Content: otherText, ChangedAt: "2026-10-01T00:00:00Z"}); errorValue != nil {
		t.Fatalf("hold another source: %v", errorValue)
	}
}

func TestRecordIndexAnswersOnlyWhatRisesAboveItsSourcesFrame(t *testing.T) {
	index := memorytest.OpenEmbeddingWith(t, rentEmbedder()).RecordIndex()
	holdRentRecords(t, index)

	rises, errorValue := index.Nearest(context.Background(), documentsName, rentQuestion, 0.1, 10)

	if errorValue != nil || len(rises) != 1 || rises[0].ID != "lease" {
		t.Fatalf("expected only the lease to rise above the frame, got %+v, %v", rises, errorValue)
	}
}

func TestRecordIndexAnswersNothingForASourceWithoutAFrame(t *testing.T) {
	index := memorytest.OpenEmbeddingWith(t, rentEmbedder()).RecordIndex()
	holdRentRecords(t, index)

	rises, errorValue := index.Nearest(context.Background(), "another_recall", rentQuestion, 0.1, 10)

	if errorValue != nil || len(rises) != 0 {
		t.Fatalf("expected no candidates without a frame to rise above, got %+v, %v", rises, errorValue)
	}
}

func TestRecordIndexEmbedsAnEntryAgainOnlyWhenItChanged(t *testing.T) {
	embedder := rentEmbedder()
	index := memorytest.OpenEmbeddingWith(t, embedder).RecordIndex()
	holdRentRecords(t, index)
	ctx := context.Background()

	unchanged := index.Hold(ctx, documentsName, memory.RecordEntry{ID: "lease", Content: leaseText, ChangedAt: "2026-10-01T00:00:00Z"})
	changed := index.Hold(ctx, documentsName, memory.RecordEntry{ID: "lease", Content: leaseText, ChangedAt: "2026-10-02T00:00:00Z"})

	if unchanged != nil || changed != nil || embedder.embedded[leaseText] != 2 {
		t.Fatalf("expected the lease embedded once more after it changed, got %d (%v, %v)", embedder.embedded[leaseText], unchanged, changed)
	}
}