- services/rag: knowledge_docs store — vector search via the shared embedder (HNSW index, graceful ILIKE fallback), list/delete scoped to the user, search intentionally global for the single-user deployment - gateway: GET/POST /knowledge/docs, DELETE /knowledge/docs/:id, POST /knowledge/search - plugins/knowledge: knowledge_search and knowledge_ingest tools so the persona can save and recall information mid-conversation - main: migrations, store wiring, plugin registration
69 lines
1.8 KiB
Go
69 lines
1.8 KiB
Go
package knowledge
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.yeij.top/AskaEth/Cyrene_v2/services/rag"
|
|
)
|
|
|
|
type fakeStore struct {
|
|
docs []rag.Document
|
|
}
|
|
|
|
func (f *fakeStore) Ingest(_ context.Context, userID, title, content string) (*rag.Document, error) {
|
|
d := &rag.Document{ID: int64(len(f.docs) + 1), UserID: userID, Title: title, Content: content}
|
|
f.docs = append(f.docs, *d)
|
|
return d, nil
|
|
}
|
|
|
|
func (f *fakeStore) Search(_ context.Context, _, query string, topK int) ([]rag.Document, error) {
|
|
var out []rag.Document
|
|
for _, d := range f.docs {
|
|
if strings.Contains(d.Content, query) || strings.Contains(d.Title, query) {
|
|
out = append(out, d)
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func TestIngestAndSearch(t *testing.T) {
|
|
p := New(&fakeStore{})
|
|
ctx := context.Background()
|
|
|
|
out, err := p.Execute(ctx, "knowledge_ingest", map[string]any{"title": "火锅攻略", "content": "楼下那家火锅店周三半价"})
|
|
if err != nil {
|
|
t.Fatalf("ingest: %v", err)
|
|
}
|
|
if !strings.Contains(out, "火锅攻略") {
|
|
t.Errorf("ingest output: %q", out)
|
|
}
|
|
|
|
out, err = p.Execute(ctx, "knowledge_search", map[string]any{"query": "火锅"})
|
|
if err != nil {
|
|
t.Fatalf("search: %v", err)
|
|
}
|
|
if !strings.Contains(out, "周三半价") {
|
|
t.Errorf("search output: %q", out)
|
|
}
|
|
}
|
|
|
|
func TestSearchEmpty(t *testing.T) {
|
|
p := New(&fakeStore{})
|
|
out, err := p.Execute(context.Background(), "knowledge_search", map[string]any{"query": "不存在"})
|
|
if err != nil || !strings.Contains(out, "没有相关内容") {
|
|
t.Errorf("out=%q err=%v", out, err)
|
|
}
|
|
}
|
|
|
|
func TestUnavailableStore(t *testing.T) {
|
|
p := New(nil)
|
|
if p.Enabled() {
|
|
t.Error("plugin should be disabled without store")
|
|
}
|
|
if _, err := p.Execute(context.Background(), "knowledge_search", map[string]any{"query": "x"}); err == nil {
|
|
t.Error("expected error with nil store")
|
|
}
|
|
}
|