This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/server/handlers/memory_inject.go
2026-03-19 18:50:27 +00:00

101 lines
2.6 KiB
Go

package handlers
import (
"context"
"fmt"
"log"
"strings"
"chat-switchboard/knowledge"
"chat-switchboard/models"
"chat-switchboard/store"
)
// maxMemoryChars is the approximate character budget for injected memories.
const maxMemoryChars = 6000
// BuildMemoryHint loads active memories for the user (and persona if active)
// and formats them as a system prompt injection.
//
// Phase 2: if an embedder is available and the user's latest message
// is provided, it generates a query vector for semantic recall. Otherwise
// falls back to keyword/confidence ranking.
func BuildMemoryHint(ctx context.Context, stores store.Stores, embedder *knowledge.Embedder, userID, personaID, lastUserMessage string) string {
if stores.Memories == nil {
return ""
}
var pID *string
if personaID != "" {
pID = &personaID
}
var memories []models.Memory
var err error
// Try hybrid recall with embedding if available
if embedder != nil && embedder.IsConfigured(ctx) && lastUserMessage != "" {
memories, err = hybridRecallForInjection(ctx, stores, embedder, userID, pID, lastUserMessage)
}
// Fall back to keyword recall
if err != nil || len(memories) == 0 {
memories, err = stores.Memories.Recall(ctx, userID, pID, "", 30)
}
if err != nil {
log.Printf("⚠ memory injection failed: %v", err)
return ""
}
if len(memories) == 0 {
return ""
}
// Format memories as bullet points, respecting token budget
var b strings.Builder
b.WriteString("Known facts about this user (from previous conversations):\n")
totalChars := b.Len()
included := 0
for _, m := range memories {
line := fmt.Sprintf("• %s: %s\n", m.Key, m.Value)
if totalChars+len(line) > maxMemoryChars {
break
}
b.WriteString(line)
totalChars += len(line)
included++
}
if included == 0 {
return ""
}
log.Printf("🧠 Injected %d memories for user %s (persona: %s)", included, userID, personaID)
return b.String()
}
// hybridRecallForInjection generates a query vector from the user's message
// and performs hybrid semantic + keyword recall.
func hybridRecallForInjection(ctx context.Context, stores store.Stores, embedder *knowledge.Embedder, userID string, personaID *string, message string) ([]models.Memory, error) {
text := message
if len(text) > 2000 {
text = text[:2000]
}
var teamID *string
if stores.Teams != nil {
ids, _ := stores.Teams.GetUserTeamIDs(ctx, userID)
if len(ids) > 0 {
teamID = &ids[0]
}
}
result, err := embedder.EmbedChunks(ctx, userID, teamID, []string{text})
if err != nil || len(result.Vectors) == 0 {
return nil, err
}
return stores.Memories.RecallHybrid(ctx, userID, personaID, "", result.Vectors[0], 30)
}