128 lines
4.0 KiB
Go
128 lines
4.0 KiB
Go
package domain
|
|
|
|
import (
|
|
"sort"
|
|
"strings"
|
|
|
|
"git.cin.su/m/Assistant/contracts/interaction"
|
|
)
|
|
|
|
// PersonalMemory is a rebuildable context projection. Its source of truth is
|
|
// the append-only human utterance history; it is deliberately not a second
|
|
// canonical store. Each fact retains the source utterance and confidence so a
|
|
// later cognitive or capability route can distinguish a human statement from
|
|
// an externally verified fact.
|
|
type PersonalMemory struct {
|
|
Facts []PersonalMemoryFact
|
|
}
|
|
|
|
type PersonalMemoryFact struct {
|
|
Kind string
|
|
Value string
|
|
ConfidenceMillis int64
|
|
SourceUtteranceID interaction.HumanUtteranceID
|
|
ObservedAtUnixMillis int64
|
|
}
|
|
|
|
const personalMemoryCurrentLocation = "CURRENT_LOCATION"
|
|
|
|
// CompilePersonalMemory turns direct, first-person human statements into a
|
|
// small typed context. It deliberately preserves preferences as statements
|
|
// rather than pretending that an arbitrary preference is a verified fact.
|
|
func CompilePersonalMemory(history []interaction.HumanUtterance) PersonalMemory {
|
|
latestLocation := PersonalMemoryFact{}
|
|
preferences := make([]PersonalMemoryFact, 0, 4)
|
|
for _, utterance := range history {
|
|
if location := personalCurrentLocation(utterance.Content); location != "" {
|
|
fact := PersonalMemoryFact{
|
|
Kind: personalMemoryCurrentLocation,
|
|
Value: location,
|
|
ConfidenceMillis: 950,
|
|
SourceUtteranceID: utterance.ID,
|
|
ObservedAtUnixMillis: utterance.ReceivedAtUnixMillis,
|
|
}
|
|
if fact.ObservedAtUnixMillis >= latestLocation.ObservedAtUnixMillis {
|
|
latestLocation = fact
|
|
}
|
|
}
|
|
if preference := personalPreference(utterance.Content); preference != "" {
|
|
preferences = append(preferences, PersonalMemoryFact{
|
|
Kind: "PREFERENCE",
|
|
Value: preference,
|
|
ConfidenceMillis: 900,
|
|
SourceUtteranceID: utterance.ID,
|
|
ObservedAtUnixMillis: utterance.ReceivedAtUnixMillis,
|
|
})
|
|
}
|
|
}
|
|
result := PersonalMemory{Facts: make([]PersonalMemoryFact, 0, 1+len(preferences))}
|
|
if latestLocation.Kind != "" {
|
|
result.Facts = append(result.Facts, latestLocation)
|
|
}
|
|
sort.SliceStable(preferences, func(left, right int) bool {
|
|
return preferences[left].ObservedAtUnixMillis > preferences[right].ObservedAtUnixMillis
|
|
})
|
|
if len(preferences) > 8 {
|
|
preferences = preferences[:8]
|
|
}
|
|
result.Facts = append(result.Facts, preferences...)
|
|
return result
|
|
}
|
|
|
|
func (m PersonalMemory) CurrentLocation() string {
|
|
for _, fact := range m.Facts {
|
|
if fact.Kind == personalMemoryCurrentLocation {
|
|
return fact.Value
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// ContextText is compact, source-aware context for a cognitive or skill
|
|
// request. It contains facts rather than a copied chat transcript.
|
|
func (m PersonalMemory) ContextText() string {
|
|
lines := make([]string, 0, len(m.Facts))
|
|
for _, fact := range m.Facts {
|
|
value := strings.TrimSpace(fact.Value)
|
|
if value == "" {
|
|
continue
|
|
}
|
|
switch fact.Kind {
|
|
case personalMemoryCurrentLocation:
|
|
lines = append(lines, "Текущее место пользователя (его сообщение): "+value)
|
|
case "PREFERENCE":
|
|
lines = append(lines, "Предпочтение пользователя (его сообщение): "+value)
|
|
}
|
|
}
|
|
return strings.Join(lines, "\n")
|
|
}
|
|
|
|
func personalCurrentLocation(message string) string {
|
|
tokens := semanticTokens(message)
|
|
if len(tokens) < 4 || tokens[0] != "я" || !containsStemInTokens(tokens, "сейчас", "нахож", "жив") {
|
|
return ""
|
|
}
|
|
for index, token := range tokens {
|
|
if (token == "в" || token == "на") && index+1 < len(tokens) {
|
|
return capitalizeWord(tokens[index+1])
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func personalPreference(message string) string {
|
|
lower := strings.ToLower(strings.TrimSpace(message))
|
|
for _, cue := range []string{"мне нравится", "я люблю"} {
|
|
index := strings.Index(lower, cue)
|
|
if index < 0 {
|
|
continue
|
|
}
|
|
value := strings.TrimSpace(lower[index+len(cue):])
|
|
value = strings.Trim(value, " .,!?:;—-")
|
|
if len([]rune(value)) >= 2 {
|
|
return value
|
|
}
|
|
}
|
|
return ""
|
|
}
|