Honeycomb und Control-Patch

This commit is contained in:
2026-08-04 05:28:51 +02:00
parent 23339a8be3
commit 3af51bf888
21 changed files with 1222 additions and 79 deletions
+43
View File
@@ -161,6 +161,10 @@ func (s *Store) ClearVectorsByDimension(dim int) int {
}
func (s *Store) NodesForEmbedding() []model.Node {
return s.NodesForEmbeddingFiltered(nil)
}
func (s *Store) NodesForEmbeddingFiltered(categories []string) []model.Node {
s.mu.RLock()
defer s.mu.RUnlock()
out := []model.Node{}
@@ -168,6 +172,9 @@ func (s *Store) NodesForEmbedding() []model.Node {
if n.Kind != "knowledge" && n.Kind != "ai-think" && n.Kind != "external" {
continue
}
if !nodeMatchesCategories(n, categories) {
continue
}
if _, ok := s.vectors[n.ID]; !ok {
out = append(out, n)
}
@@ -332,6 +339,10 @@ func (s *Store) Similar(query []float64, limit int) []model.Hit {
// selection responsive even for tens of thousands of knowledge nodes while the
// rotating cursor eventually visits the complete corpus.
func (s *Store) NextPair(min float64, anchorLimit int) (model.Node, model.Node, float64, bool, int) {
return s.NextPairFiltered(min, anchorLimit, nil)
}
func (s *Store) NextPairFiltered(min float64, anchorLimit int, categories []string) (model.Node, model.Node, float64, bool, int) {
s.mu.Lock()
defer s.mu.Unlock()
@@ -340,6 +351,9 @@ func (s *Store) NextPair(min float64, anchorLimit int) (model.Node, model.Node,
if n.Kind != "knowledge" && n.Kind != "ai-think" {
continue
}
if !nodeMatchesCategories(n, categories) {
continue
}
if v, ok := s.vectors[n.ID]; ok && len(v) > 0 {
nodes = append(nodes, n)
}
@@ -439,6 +453,35 @@ func (s *Store) ConnectingEdges(ids []string) []string {
}
return out
}
func nodeMatchesCategories(n model.Node, filters []string) bool {
if len(filters) == 0 {
return true
}
wanted := make(map[string]struct{}, len(filters))
for _, filter := range filters {
filter = strings.ToLower(strings.TrimSpace(filter))
if filter != "" {
wanted[filter] = struct{}{}
}
}
if len(wanted) == 0 {
return true
}
if _, ok := wanted["*"]; ok {
return true
}
if len(n.Categories) == 0 {
_, ok := wanted["__uncategorized__"]
return ok
}
for _, category := range n.Categories {
if _, ok := wanted[strings.ToLower(strings.TrimSpace(category))]; ok {
return true
}
}
return false
}
func edgeBetweenLocked(edges map[string]model.Edge, a, b string) bool {
for _, e := range edges {
if (e.Source == a && e.Target == b) || (e.Source == b && e.Target == a) {
+31
View File
@@ -69,3 +69,34 @@ func TestNextPairUsesBoundedRotatingAnchors(t *testing.T) {
t.Fatalf("rotating anchor did not reach the next semantic region, ok=%v comparisons=%d", ok, comparisons)
}
}
func TestCategoryFiltersLimitEmbeddingAndThinkingCandidates(t *testing.T) {
s, err := Open(t.TempDir())
if err != nil {
t.Fatal(err)
}
nodes := []model.Node{
{ID: "net-a", Kind: "knowledge", Label: "Net A", Categories: []string{"Netzwerk"}, Origin: "test"},
{ID: "net-b", Kind: "knowledge", Label: "Net B", Categories: []string{"Netzwerk"}, Origin: "test"},
{ID: "app-a", Kind: "knowledge", Label: "App A", Categories: []string{"Applikation"}, Origin: "test"},
{ID: "none", Kind: "knowledge", Label: "Ohne", Origin: "test"},
}
for _, node := range nodes {
s.UpsertNode(node)
}
pending := s.NodesForEmbeddingFiltered([]string{"Netzwerk"})
if len(pending) != 2 {
t.Fatalf("expected two network embeddings, got %d", len(pending))
}
uncategorized := s.NodesForEmbeddingFiltered([]string{"__uncategorized__"})
if len(uncategorized) != 1 || uncategorized[0].ID != "none" {
t.Fatalf("unexpected uncategorized nodes: %#v", uncategorized)
}
for _, node := range nodes {
s.SetVector(node.ID, []float64{1, .01})
}
a, b, _, ok, _ := s.NextPairFiltered(.5, 8, []string{"Netzwerk"})
if !ok || a.Categories[0] != "Netzwerk" || b.Categories[0] != "Netzwerk" {
t.Fatalf("thinking filter returned wrong pair: ok=%v a=%+v b=%+v", ok, a, b)
}
}