mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-10-04 00:03:59 +00:00
refactor: pluginize text cleaning and tool NoMemory control
- SDK: ToolDef.NoMemory field, PluginSDK.RegisterTextCleaner/TextCleaners - Registry: aggregate text cleaners from plugins, expose CleanText() - Memory: replace hardcoded QQ regex CleanTemplateText with dynamic CleanText/SetTextCleaner - StageHost: add ToolDef(name) lookup - eventloop: check ToolDef.NoMemory before emitMemoryCandidate - context/Prune: replace hardcoded agentcli/terminal source filter with ToolsUsed NoMemory check - agentcli/cmd: mark tools with NoMemory: true - main.go: wire memory.SetTextCleaner(pluginReg.CleanText)
This commit is contained in:
@ -165,6 +165,11 @@ func New(cfg AgentConfig) *Agent {
|
||||
cfg.DocStore.ReindexWithVectorizer(embedder)
|
||||
}
|
||||
|
||||
rc := NewRelevanceContext(cfg.ContextSavePath, embedder)
|
||||
if cfg.StageHost != nil {
|
||||
rc.SetToolDefLookup(cfg.StageHost.ToolDef)
|
||||
}
|
||||
|
||||
return &Agent{
|
||||
id: cfg.ID,
|
||||
startTime: time.Now(),
|
||||
@ -175,7 +180,7 @@ func New(cfg AgentConfig) *Agent {
|
||||
indexer: cfg.Indexer,
|
||||
skills: cfg.Skills,
|
||||
tracker: cfg.Tracker,
|
||||
context: NewRelevanceContext(cfg.ContextSavePath, embedder),
|
||||
context: rc,
|
||||
systemPrompt: cfg.SystemPrompt,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
|
||||
@ -13,6 +13,7 @@ import (
|
||||
"gitcode.com/JianFeeeee/HomeAgent/internal/memory"
|
||||
"gitcode.com/JianFeeeee/HomeAgent/internal/memory/document"
|
||||
"gitcode.com/JianFeeeee/HomeAgent/internal/memory/vector"
|
||||
sdk "gitcode.com/JianFeeeee/HomeAgent/internal/sdk"
|
||||
)
|
||||
|
||||
type ContextEvent struct {
|
||||
@ -27,12 +28,13 @@ type ContextEvent struct {
|
||||
const contextFlushInterval = 5 * time.Second
|
||||
|
||||
type RelevanceContext struct {
|
||||
mu sync.Mutex
|
||||
events []*ContextEvent
|
||||
embedder *memory.StaticEmbedder
|
||||
savePath string
|
||||
saveTimer *time.Timer
|
||||
dirty bool
|
||||
mu sync.Mutex
|
||||
events []*ContextEvent
|
||||
embedder *memory.StaticEmbedder
|
||||
savePath string
|
||||
saveTimer *time.Timer
|
||||
dirty bool
|
||||
toolDefLookup func(name string) *sdk.ToolDef
|
||||
}
|
||||
|
||||
func NewRelevanceContext(savePath string, embedder *memory.StaticEmbedder) *RelevanceContext {
|
||||
@ -46,6 +48,12 @@ func NewRelevanceContext(savePath string, embedder *memory.StaticEmbedder) *Rele
|
||||
return rc
|
||||
}
|
||||
|
||||
func (c *RelevanceContext) SetToolDefLookup(fn func(name string) *sdk.ToolDef) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.toolDefLookup = fn
|
||||
}
|
||||
|
||||
func (c *RelevanceContext) load() {
|
||||
data, err := os.ReadFile(c.savePath)
|
||||
if err != nil {
|
||||
@ -56,7 +64,7 @@ func (c *RelevanceContext) load() {
|
||||
return
|
||||
}
|
||||
for _, evt := range events {
|
||||
evt.Input = memory.CleanTemplateText(evt.Input)
|
||||
evt.Input = memory.CleanText(evt.Input)
|
||||
evt.Vector = c.computeVector(evt)
|
||||
}
|
||||
c.events = events
|
||||
@ -65,11 +73,11 @@ func (c *RelevanceContext) load() {
|
||||
func textForVector(evt *ContextEvent) string {
|
||||
switch {
|
||||
case evt.Source == "agent" && evt.Response != "":
|
||||
return memory.CleanTemplateText(evt.Response)
|
||||
return memory.CleanText(evt.Response)
|
||||
case evt.Source == "cold_storage":
|
||||
return memory.CleanTemplateText(evt.Input + " " + evt.Response)
|
||||
return memory.CleanText(evt.Input + " " + evt.Response)
|
||||
default:
|
||||
return memory.CleanTemplateText(evt.Input)
|
||||
return memory.CleanText(evt.Input)
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,7 +103,7 @@ func (c *RelevanceContext) Append(evt ContextEvent) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
evt.Input = memory.CleanTemplateText(evt.Input)
|
||||
evt.Input = memory.CleanText(evt.Input)
|
||||
evt.Vector = c.computeVector(&evt)
|
||||
c.events = append(c.events, &evt)
|
||||
|
||||
@ -193,7 +201,7 @@ func (c *RelevanceContext) Prune(currentInput string, topK int, docStore *docume
|
||||
if docStore != nil && len(archive) > 0 {
|
||||
var filtered []scored
|
||||
for _, s := range archive {
|
||||
if s.event.Source == "agentcli" || s.event.Source == "terminal" {
|
||||
if hasNoMemoryTool(s.event.ToolsUsed, c.toolDefLookup) {
|
||||
continue
|
||||
}
|
||||
filtered = append(filtered, s)
|
||||
@ -257,3 +265,15 @@ func (c *RelevanceContext) Len() int {
|
||||
defer c.mu.Unlock()
|
||||
return len(c.events)
|
||||
}
|
||||
|
||||
func hasNoMemoryTool(toolsUsed []string, lookup func(string) *sdk.ToolDef) bool {
|
||||
if lookup == nil {
|
||||
return false
|
||||
}
|
||||
for _, name := range toolsUsed {
|
||||
if def := lookup(name); def != nil && def.NoMemory {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import (
|
||||
"time"
|
||||
|
||||
"gitcode.com/JianFeeeee/HomeAgent/internal/memory"
|
||||
sdk "gitcode.com/JianFeeeee/HomeAgent/internal/sdk"
|
||||
)
|
||||
|
||||
func newTestCtx() *RelevanceContext {
|
||||
@ -237,6 +238,40 @@ func containsStr(s, substr string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func TestHasNoMemoryTool(t *testing.T) {
|
||||
host := NewStageHost()
|
||||
host.RegisterTool("no_mem_tool", sdk.ToolDef{Name: "no_mem_tool", NoMemory: true}, nil)
|
||||
host.RegisterTool("mem_tool", sdk.ToolDef{Name: "mem_tool"}, nil)
|
||||
lookup := host.ToolDef
|
||||
|
||||
gotNil := hasNoMemoryTool([]string{"no_mem_tool"}, nil)
|
||||
if gotNil {
|
||||
t.Error("hasNoMemoryTool with nil lookup should return false")
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
toolsUsed []string
|
||||
want bool
|
||||
}{
|
||||
{"empty tools", nil, false},
|
||||
{"no matching tool", []string{"unknown"}, false},
|
||||
{"tool without NoMemory", []string{"mem_tool"}, false},
|
||||
{"tool with NoMemory", []string{"no_mem_tool"}, true},
|
||||
{"mixed tools, first is no_memory", []string{"no_mem_tool", "mem_tool"}, true},
|
||||
{"mixed tools, last is no_memory", []string{"mem_tool", "no_mem_tool"}, true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := hasNoMemoryTool(tt.toolsUsed, lookup)
|
||||
if got != tt.want {
|
||||
t.Errorf("hasNoMemoryTool(%v) = %v, want %v", tt.toolsUsed, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func splitLines(s string) []string {
|
||||
var lines []string
|
||||
start := 0
|
||||
|
||||
@ -205,7 +205,7 @@ func (a *Agent) processMediaInput(evt *agentIO.InputEvent) {
|
||||
|
||||
a.emitResponse(evt, response)
|
||||
|
||||
if !stageCtx.NoMemory {
|
||||
if !stageCtx.NoMemory && !a.hasNoMemoryTool(toolsUsed) {
|
||||
a.emitMemoryCandidate(evt.Source, fallback, response, toolsUsed)
|
||||
}
|
||||
}
|
||||
@ -334,7 +334,7 @@ func (a *Agent) processTextInput(evt *agentIO.InputEvent, input string) {
|
||||
|
||||
a.emitResponse(evt, response)
|
||||
|
||||
if !stageCtx.NoMemory {
|
||||
if !stageCtx.NoMemory && !a.hasNoMemoryTool(toolsUsed) {
|
||||
a.emitMemoryCandidate(evt.Source, input, response, toolsUsed)
|
||||
}
|
||||
}
|
||||
@ -386,6 +386,15 @@ func (a *Agent) emitResponse(evt *agentIO.InputEvent, response string) {
|
||||
a.runStage(sdk.StageAfterOutput, stageCtx)
|
||||
}
|
||||
|
||||
func (a *Agent) hasNoMemoryTool(toolsUsed []string) bool {
|
||||
for _, name := range toolsUsed {
|
||||
if def := a.stageHost.ToolDef(name); def != nil && def.NoMemory {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (a *Agent) drainInterrupts() []string {
|
||||
var out []string
|
||||
for {
|
||||
|
||||
@ -54,6 +54,17 @@ func (h *StageHost) GetToolDefs() []sdk.ToolDef {
|
||||
return defs
|
||||
}
|
||||
|
||||
func (h *StageHost) ToolDef(name string) *sdk.ToolDef {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
for _, def := range h.toolDefs {
|
||||
if def.Name == name {
|
||||
return &def
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *StageHost) ExecuteTool(name string, args map[string]interface{}) (ret interface{}, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
|
||||
@ -197,3 +197,54 @@ func TestStageHostMultipleTools(t *testing.T) {
|
||||
t.Errorf("expected from_p2, got %v", r2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStageHostToolDefLookup(t *testing.T) {
|
||||
host := NewStageHost()
|
||||
host.RegisterTool("tool_a", sdk.ToolDef{Name: "tool_a", NoMemory: true}, nil)
|
||||
host.RegisterTool("tool_b", sdk.ToolDef{Name: "tool_b"}, nil)
|
||||
|
||||
def := host.ToolDef("tool_a")
|
||||
if def == nil {
|
||||
t.Fatal("expected tool_a to be found")
|
||||
}
|
||||
if !def.NoMemory {
|
||||
t.Error("tool_a should have NoMemory=true")
|
||||
}
|
||||
|
||||
def = host.ToolDef("tool_b")
|
||||
if def == nil {
|
||||
t.Fatal("expected tool_b to be found")
|
||||
}
|
||||
if def.NoMemory {
|
||||
t.Error("tool_b should have NoMemory=false")
|
||||
}
|
||||
|
||||
def = host.ToolDef("nonexistent")
|
||||
if def != nil {
|
||||
t.Errorf("expected nil for nonexistent tool, got %v", def)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStageHostToolDefNoMemoryStored(t *testing.T) {
|
||||
host := NewStageHost()
|
||||
host.RegisterTool("mem_tool", sdk.ToolDef{Name: "mem_tool", NoMemory: true}, nil)
|
||||
host.RegisterTool("normal_tool", sdk.ToolDef{Name: "normal_tool", NoMemory: false}, nil)
|
||||
|
||||
defs := host.GetToolDefs()
|
||||
found := map[string]bool{}
|
||||
for _, d := range defs {
|
||||
found[d.Name] = d.NoMemory
|
||||
}
|
||||
|
||||
if v, ok := found["mem_tool"]; !ok {
|
||||
t.Error("mem_tool not found in defs")
|
||||
} else if !v {
|
||||
t.Error("mem_tool.NoMemory should be true")
|
||||
}
|
||||
|
||||
if v, ok := found["normal_tool"]; !ok {
|
||||
t.Error("normal_tool not found in defs")
|
||||
} else if v {
|
||||
t.Error("normal_tool.NoMemory should be false")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user