+38
-13
@@ -124,6 +124,10 @@ func New(cfg config.Config, g *graph.Store, b *activity.Broker) *Engine {
|
||||
}
|
||||
nodes = append(nodes, ollama.NodeConfig{Name: name, URL: rawURL, Weight: weight})
|
||||
}
|
||||
clearedVectors := g.ConfigureEmbeddingModel(cfg.EmbeddingModel)
|
||||
if clearedVectors > 0 && b != nil {
|
||||
b.Publish(model.Activity{Type: "embedding.model_changed", Source: "brain", Phase: "learning", Message: fmt.Sprintf("Embedding-Modell geändert · %d Vektoren werden neu gelernt", clearedVectors), Strength: .65, Metadata: map[string]any{"embedding_model": cfg.EmbeddingModel, "cleared_vectors": clearedVectors}})
|
||||
}
|
||||
pool := ollama.NewPool(ollama.PoolConfig{
|
||||
Nodes: nodes, RoutingMode: cfg.OllamaRoutingMode, NodeMaxInflight: cfg.OllamaNodeMaxInflight,
|
||||
HealthInterval: cfg.OllamaHealthInterval, FailureCooldown: cfg.OllamaFailureCooldown,
|
||||
@@ -361,16 +365,23 @@ func (e *Engine) idle(ctx context.Context) {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
s := e.Graph.Snapshot()
|
||||
if len(s.Nodes) == 0 {
|
||||
n, ok := e.Graph.IdleNode(time.Now().Unix() / 7)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
idx := int(time.Now().Unix()/7) % len(s.Nodes)
|
||||
n := s.Nodes[idx]
|
||||
e.Broker.Publish(model.Activity{Type: "brain.idle", Source: "brain", Phase: "idle", Message: "Leise Hintergrundaktivität", NodeIDs: []string{n.ID}, Strength: .18})
|
||||
}
|
||||
}
|
||||
}
|
||||
func (e *Engine) configureEmbeddingDigest() int {
|
||||
for _, node := range e.Ollama.NodeStatuses() {
|
||||
if node.Healthy && node.Compatible && node.EmbeddingModel && strings.TrimSpace(node.EmbeddingDigest) != "" {
|
||||
return e.Graph.ConfigureEmbeddingIdentity(e.Cfg.EmbeddingModel, node.EmbeddingDigest)
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (e *Engine) Scan(ctx context.Context) error {
|
||||
if !e.LearningEnabled() {
|
||||
return ErrLearningDisabled
|
||||
@@ -394,6 +405,9 @@ func (e *Engine) Scan(ctx context.Context) error {
|
||||
e.ensureFallbackEmbeddings()
|
||||
e.setOllamaOK(false)
|
||||
} else {
|
||||
if cleared := e.configureEmbeddingDigest(); cleared > 0 {
|
||||
e.Broker.Publish(model.Activity{Type: "embedding.identity_changed", Source: "brain", Phase: "learning", Message: fmt.Sprintf("Embedding-Digest geändert · %d Vektoren werden neu gelernt", cleared), Strength: .7, Metadata: map[string]any{"embedding_model": e.Cfg.EmbeddingModel, "cleared_vectors": cleared}})
|
||||
}
|
||||
// Local fallback vectors use 256 dimensions. Once Ollama becomes available,
|
||||
// discard those placeholders and replace them with real model embeddings.
|
||||
e.Graph.ClearVectorsByDimension(256)
|
||||
@@ -409,8 +423,8 @@ func (e *Engine) Scan(ctx context.Context) error {
|
||||
e.lastScan = time.Now().UTC()
|
||||
e.stateMu.Unlock()
|
||||
if e.Graph.Version() != beforeVersion {
|
||||
s := e.Graph.Snapshot()
|
||||
e.Broker.Publish(model.Activity{Type: "graph.updated", Source: "brain", Phase: "indexed", Message: fmt.Sprintf("%d Wissenselemente · %d Nodes · %d Edges", count, len(s.Nodes), len(s.Edges)), Strength: .55, Metadata: map[string]any{"nodes": len(s.Nodes), "edges": len(s.Edges), "knowledge_elements": count}})
|
||||
nodes, edges, _ := e.Graph.Counts()
|
||||
e.Broker.Publish(model.Activity{Type: "graph.updated", Source: "brain", Phase: "indexed", Message: fmt.Sprintf("%d Wissenselemente · %d Nodes · %d Edges", count, nodes, edges), Strength: .55, Metadata: map[string]any{"nodes": nodes, "edges": edges, "knowledge_elements": count}})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -631,12 +645,15 @@ func (e *Engine) enrichOne(ctx context.Context, trigger string) (EnrichOutcome,
|
||||
researchStarted := time.Now()
|
||||
startMetadata := map[string]any{"trigger": trigger, "research_id": researchID, "research_query": decision.ResearchQuery, "source_label": a.Label, "target_label": b.Label, "animation_min_ms": 2000}
|
||||
e.Broker.Publish(model.Activity{Type: "research.started", Source: "searxng", Phase: "research", NodeIDs: []string{a.ID, b.ID}, Message: "Unklarheit erkannt · SearXNG durchsucht externe Quellen", Strength: .9, Metadata: startMetadata})
|
||||
results, err := e.Research.Search(ctx, decision.ResearchQuery, 4)
|
||||
results, diagnostic, err := e.Research.SearchDetailed(ctx, decision.ResearchQuery, 4)
|
||||
if err != nil {
|
||||
slog.Warn("research failed", "error", err)
|
||||
e.Broker.Publish(model.Activity{Type: "research.failed", Source: "searxng", Phase: "research", NodeIDs: []string{a.ID, b.ID}, Message: "SearXNG-Recherche ist fehlgeschlagen", Strength: .35, Metadata: mergeResearchMetadata(startMetadata, map[string]any{"error": err.Error(), "duration_ms": time.Since(researchStarted).Milliseconds()})})
|
||||
metadata := mergeResearchMetadata(startMetadata, researchDiagnosticMetadata(diagnostic))
|
||||
metadata["error"] = err.Error()
|
||||
metadata["duration_ms"] = time.Since(researchStarted).Milliseconds()
|
||||
slog.Warn("research failed", "query", decision.ResearchQuery, "base_url", diagnostic.BaseURL, "kind", diagnostic.ErrorKind, "http_status", diagnostic.HTTPStatus, "duration_ms", diagnostic.DurationMS, "error", err)
|
||||
e.Broker.Publish(model.Activity{Type: "research.failed", Source: "searxng", Phase: "research", NodeIDs: []string{a.ID, b.ID}, Message: "SearXNG-Recherche ist fehlgeschlagen", Strength: .35, Metadata: metadata})
|
||||
} else {
|
||||
resultMetadata := researchEventMetadata(trigger, researchID, decision.ResearchQuery, results, time.Since(researchStarted))
|
||||
resultMetadata := mergeResearchMetadata(researchEventMetadata(trigger, researchID, decision.ResearchQuery, results, time.Since(researchStarted)), researchDiagnosticMetadata(diagnostic))
|
||||
message := fmt.Sprintf("SearXNG hat %d verwertbare Webquellen geliefert", len(results))
|
||||
if len(results) == 0 {
|
||||
message = "SearXNG hat keine verwertbaren Webquellen geliefert"
|
||||
@@ -706,10 +723,10 @@ func (e *Engine) addResearch(a, b model.Node, results []model.ResearchResult) re
|
||||
return uniqueResearchRefs(refs)
|
||||
}
|
||||
func (e *Engine) Status() map[string]any {
|
||||
s := e.Graph.Snapshot()
|
||||
nodes, edges, version := e.Graph.Counts()
|
||||
e.stateMu.RLock()
|
||||
status := map[string]any{
|
||||
"ok": true, "nodes": len(s.Nodes), "edges": len(s.Edges), "version": s.Version,
|
||||
"ok": true, "nodes": nodes, "edges": edges, "version": version,
|
||||
"last_scan": e.lastScan, "last_enrich": e.lastEnrich, "last_enrich_attempt": e.lastAttempt,
|
||||
"next_enrich": e.nextEnrich, "ollama_ok": e.ollamaOK, "auto_enrich": e.Cfg.AutoEnrich,
|
||||
"enrich_running": e.enrichRunning, "enrich_trigger": e.enrichTrigger, "enrich_result": e.enrichResult,
|
||||
@@ -722,7 +739,8 @@ func (e *Engine) Status() map[string]any {
|
||||
"enrich_interval": e.Cfg.EnrichInterval.String(),
|
||||
"enrich_batch_size": e.Cfg.EnrichBatchSize, "enrich_anchors": e.Cfg.EnrichAnchors,
|
||||
"research_enabled": e.Cfg.ResearchEnabled, "chat_model": e.Cfg.ChatModel, "embedding_model": e.Cfg.EmbeddingModel,
|
||||
"ollama_pool": e.Ollama.PoolStatus(), "persistence": e.Persistence.Status(),
|
||||
"searxng": e.ResearchStatus(),
|
||||
"ollama_pool": e.Ollama.PoolStatus(), "persistence": e.Persistence.Status(), "graph_storage": e.Graph.StorageStatus(),
|
||||
"runtime_settings": e.RuntimeSettings(),
|
||||
}
|
||||
if e.GLPIKB != nil {
|
||||
@@ -738,6 +756,13 @@ func (e *Engine) Flush(ctx context.Context) error {
|
||||
return e.Persistence.Flush(ctx, "manual")
|
||||
}
|
||||
|
||||
func (e *Engine) ExportGraph(ctx context.Context, destination string) error {
|
||||
if err := e.Persistence.Flush(ctx, "export"); err != nil {
|
||||
return err
|
||||
}
|
||||
return e.Graph.Export(ctx, destination)
|
||||
}
|
||||
|
||||
func (e *Engine) SyncGLPIKB(ctx context.Context) error {
|
||||
if !e.LearningEnabled() {
|
||||
return ErrLearningDisabled
|
||||
|
||||
Reference in New Issue
Block a user