Files
Cyrene_v2/internal/runtime/memory/extract.go
T

82 lines
2.7 KiB
Go

package memory
import (
"context"
"strings"
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model"
)
// Extractor derives candidate memory items from a conversation (design §1).
//
// The production path is an LLM-driven asynchronous extractor that denoises and
// deduplicates each turn; that is deferred to milestone M4, and its integration
// point is this interface. The M2 implementation is the rule-based
// ManualExtractor, which only records what the user explicitly asks to remember.
type Extractor interface {
// Extract returns memory items worth persisting from conversation. ctx
// controls cancellation; a canceled context returns an error.
Extract(ctx context.Context, conversation []model.Message) ([]MemoryItem, error)
}
// ManualExtractor implements the manual "记住 X" extraction path without an LLM.
// It scans user turns for a leading remember directive and produces a memory
// item for each, falling back to no item for ordinary dialogue. It is the
// deterministic counterpart to the M4 LLM extractor.
type ManualExtractor struct {
personaID string
roleID string
}
// NewManualExtractor builds a ManualExtractor whose extracted items are scoped
// to personaID and roleID, isolated from other personas (design §4).
func NewManualExtractor(personaID, roleID string) *ManualExtractor {
return &ManualExtractor{personaID: personaID, roleID: roleID}
}
// Extract returns one memory item (kind relationship, persona scope) per user
// turn that asks the agent to remember something. A leading "记住"/"请记住"
// directive is stripped from the stored content; turns without one are skipped.
func (e *ManualExtractor) Extract(ctx context.Context, conversation []model.Message) ([]MemoryItem, error) {
if err := ctx.Err(); err != nil {
return nil, err
}
items := make([]MemoryItem, 0)
for _, msg := range conversation {
if msg.Role != model.RoleUser {
continue
}
text := strings.TrimSpace(messageText(msg))
content, ok := rememberDirective(text)
if !ok {
continue
}
items = append(items, MemoryItem{
PersonaID: e.personaID,
RoleID: e.roleID,
Scope: ScopePersona,
Kind: KindRelationship,
Content: content,
Confidence: 1.0,
DecayWeight: 1.0,
})
}
return items, nil
}
// rememberDirective strips the "记住" / "请记住" prefix when text is a remember
// instruction. It returns the remembered content and true, or ok=false when the
// text is not an explicit remember request.
func rememberDirective(text string) (string, bool) {
for _, prefix := range []string{"请记住", "记住"} {
if strings.HasPrefix(text, prefix) {
content := strings.TrimSpace(strings.TrimPrefix(text, prefix))
if content == "" {
return "", false
}
return content, true
}
}
return "", false
}