mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-21 09:28:14 +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:
246
plan.md
Normal file
246
plan.md
Normal file
@ -0,0 +1,246 @@
|
||||
# 插件化改造 — 实施计划
|
||||
|
||||
## 问题
|
||||
|
||||
当前 core 层两处硬编码耦合:
|
||||
|
||||
1. **文本清洗**:`internal/memory/clean_text.go` 硬编码 6 个 QQ 正则,context/indexer/cut/VertorizeClean 各环节直接调用 `CleanTemplateText`
|
||||
|
||||
2. **工具输出不进记忆**:`internal/agent/core/context.go:196` 硬编码 source 名过滤
|
||||
```go
|
||||
if s.event.Source == "agentcli" || s.event.Source == "terminal" {
|
||||
continue
|
||||
}
|
||||
```
|
||||
意图是 `cmd_run`、`terminal_*` 等工具的产出不归档到 document memory,但手段是查 source 而不是查工具定义,且只覆盖了 Prune 路径(document),没有覆盖 `emitMemoryCandidate` 路径(text + graph memory)。
|
||||
|
||||
同时 `eventloop.go:337` 的 `!stageCtx.NoMemory` 检查是命令式的(`InjectTextNoMemory` 设 payload flag),不是声明式的工具级控制。
|
||||
|
||||
## 目标
|
||||
|
||||
| 问题 | 方案 |
|
||||
|------|------|
|
||||
| 文本清洗 | 插件通过 `s.RegisterTextCleaner()` 注册清洗函数,core 遍历执行 |
|
||||
| 工具记忆控制 | `ToolDef` 增加 `NoMemory bool` 字段,插件声明式标记工具;core 在 emitMemoryCandidate 和 Prune 两处自动跳过 |
|
||||
|
||||
## SDK 修改(外部包 `homeagent-sdk`)
|
||||
|
||||
**位置:** `sdk/plugin.go`
|
||||
|
||||
### 1.1 ToolDef 增加 NoMemory
|
||||
|
||||
```go
|
||||
type ToolDef struct {
|
||||
Name string `json:"name"`
|
||||
Plugin string `json:"plugin,omitempty"`
|
||||
Description string `json:"description"`
|
||||
Parameters map[string]interface{} `json:"parameters"`
|
||||
NoMemory bool `json:"no_memory,omitempty"` // ← 新增
|
||||
}
|
||||
```
|
||||
|
||||
### 1.2 PluginSDK 增加 TextCleaner
|
||||
|
||||
```go
|
||||
// PluginSDK 新增字段
|
||||
textCleaners []func(text string) string
|
||||
|
||||
func (s *PluginSDK) RegisterTextCleaner(cleaner func(text string) string) {
|
||||
s.textCleaners = append(s.textCleaners, cleaner)
|
||||
}
|
||||
|
||||
// 框架用 — 提取所有 cleaner
|
||||
func (s *PluginSDK) TextCleaners() []func(text string) string {
|
||||
return s.textCleaners
|
||||
}
|
||||
```
|
||||
|
||||
### SDK 管理策略
|
||||
|
||||
复制 SDK 到 `_sdk_local/`,`go.mod` 加 `replace gitcode.com/JianFeeeee/homeagent-sdk => ./_sdk_local`
|
||||
|
||||
---
|
||||
|
||||
## 内部变更
|
||||
|
||||
### 2.1 Registry:聚合 text cleaners
|
||||
|
||||
**文件:** `internal/plugin/registry.go`
|
||||
|
||||
- 新增字段 `textCleaners []func(string) string`
|
||||
- `buildSDK()` 中 `plg.Start(plgSDK)` 成功后提取 `plgSDK.TextCleaners()` 到 `textCleaners`
|
||||
- 新增 `CleanText(text string) string` 依次执行所有 cleaner,无 cleaner 时返回原文本
|
||||
- `StopAll()` / `Reload()` 时重置
|
||||
|
||||
### 2.2 Memory:动态 cleaner 入口
|
||||
|
||||
**文件:** `internal/memory/clean_text.go`
|
||||
|
||||
替换硬编码 QQ 正则为动态 cleaner:
|
||||
|
||||
```go
|
||||
var globalTextCleaner func(string) string
|
||||
|
||||
func SetTextCleaner(fn func(string) string) { globalTextCleaner = fn }
|
||||
func CleanText(text string) string {
|
||||
if globalTextCleaner != nil {
|
||||
text = globalTextCleaner(text)
|
||||
}
|
||||
// 保留通用 trim
|
||||
text = strings.TrimSpace(text)
|
||||
text = strings.TrimPrefix(text, ",")
|
||||
text = strings.TrimPrefix(text, ",")
|
||||
text = strings.TrimSpace(text)
|
||||
return text
|
||||
}
|
||||
```
|
||||
|
||||
删除 `reQQ*` 正则变量。
|
||||
|
||||
### 2.3 StageHost:按名查 ToolDef
|
||||
|
||||
**文件:** `internal/agent/core/stages.go`
|
||||
|
||||
新增方法:
|
||||
|
||||
```go
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
### 2.4 Agent:两处 NoMemory 检查
|
||||
|
||||
#### 2.4a emitMemoryCandidate 路径
|
||||
|
||||
**文件:** `internal/agent/core/eventloop.go`
|
||||
|
||||
`processTextInput()` 和 `processMediaInput()` 中的 `emitMemoryCandidate` 调用前,除了检查 `stageCtx.NoMemory`,还检查本次调用的工具是否有 `NoMemory`:
|
||||
|
||||
```go
|
||||
hasNoMemoryTool := false
|
||||
for _, name := range toolsUsed {
|
||||
if def := a.stageHost.ToolDef(name); def != nil && def.NoMemory {
|
||||
hasNoMemoryTool = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !stageCtx.NoMemory && !hasNoMemoryTool {
|
||||
a.emitMemoryCandidate(...)
|
||||
}
|
||||
```
|
||||
|
||||
这样不论通过 `InjectText` 还是 `InjectTextNoMemory`,只要 LLM 调用了 `NoMemory: true` 的工具,整轮对话就不进 text memory 和 graph memory。
|
||||
|
||||
#### 2.4b Prune 归档路径
|
||||
|
||||
**文件:** `internal/agent/core/context.go`
|
||||
|
||||
删除 hardcoded source 过滤,改为检查事件中 `ToolsUsed` 是否有 `NoMemory` 工具:
|
||||
|
||||
```go
|
||||
for _, s := range archive {
|
||||
if hasNoMemoryTool(s.event.ToolsUsed) {
|
||||
continue
|
||||
}
|
||||
filtered = append(filtered, s)
|
||||
}
|
||||
```
|
||||
|
||||
`hasNoMemoryTool` 通过 `StageHost.ToolDef` 查每个工具名是否有 `NoMemory: true`。
|
||||
|
||||
### 2.5 caller CleanTemplateText → CleanText
|
||||
|
||||
| 文件 | 替换 |
|
||||
|------|------|
|
||||
| `internal/agent/core/context.go` | `memory.CleanTemplateText` → `memory.CleanText` |
|
||||
| `internal/memory/indexer.go` | 同上 |
|
||||
| `internal/memory/cut.go` | 同上 |
|
||||
| `internal/memory/clean_text.go` | 删除函数 `CleanTemplateText`,新增 `CleanText` + `SetTextCleaner` |
|
||||
|
||||
### 2.6 main.go 串联
|
||||
|
||||
**文件:** `cmd/homed/main.go`
|
||||
|
||||
`pluginReg.Load()` 之后:
|
||||
|
||||
```go
|
||||
memory.SetTextCleaner(pluginReg.CleanText)
|
||||
```
|
||||
|
||||
### 2.7 agentcli / cmd 插件:标记 NoMemory
|
||||
|
||||
**文件:** `internal/plugins/agentcli/plugin.go` 和 `internal/plugins/cmd/plugin.go`
|
||||
|
||||
在 `RegisterTool` 调用的 `ToolDef` 中加 `NoMemory: true`:
|
||||
|
||||
- `terminal_create`, `terminal_write`, `terminal_read`, `terminal_resize`, `terminal_close`, `terminal_list`
|
||||
- `cmd_run`
|
||||
|
||||
---
|
||||
|
||||
## 外部插件变更
|
||||
|
||||
### QQ 插件
|
||||
|
||||
在 `Start()` 中:
|
||||
|
||||
```go
|
||||
s.RegisterTextCleaner(func(text string) string {
|
||||
text = reQQGroupSuffix.ReplaceAllString(text, "")
|
||||
text = reQQPrivateSuffix.ReplaceAllString(text, "")
|
||||
text = reQQOldReply.ReplaceAllString(text, "")
|
||||
text = reQQOldForbid.ReplaceAllString(text, "")
|
||||
text = reQQGeneral.ReplaceAllString(text, "")
|
||||
text = reTimestamp.ReplaceAllString(text, "")
|
||||
text = reMultiSpace.ReplaceAllString(text, " ")
|
||||
return text
|
||||
})
|
||||
|
||||
s.RegisterTool("qq_get_message", sdk.ToolDef{
|
||||
Description: "获取QQ消息正文",
|
||||
Parameters: map[string]interface{}{...},
|
||||
NoMemory: true,
|
||||
}, handler)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 文件变更清单
|
||||
|
||||
| 文件 | 操作 |
|
||||
|------|------|
|
||||
| `sdk/plugin.go`(外部 SDK) | 修改:`ToolDef` 增 `NoMemory`、`PluginSDK` 增 `RegisterTextCleaner`/`TextCleaners` |
|
||||
| `go.mod` | 修改:增 `replace` 指令 |
|
||||
| `internal/plugin/registry.go` | 修改:增 cleaners 聚合 + `CleanText` |
|
||||
| `internal/memory/clean_text.go` | 修改:硬编码 → 动态 cleaner,删除 `CleanTemplateText` |
|
||||
| `internal/agent/core/stages.go` | 修改:增 `ToolDef(name)` 查找 |
|
||||
| `internal/agent/core/eventloop.go` | 修改:`emitMemoryCandidate` 前查 `ToolDef.NoMemory` |
|
||||
| `internal/agent/core/context.go` | 修改:source 过滤 → `ToolsUsed` NoMemory 检查 + `CleanTemplateText` → `CleanText` |
|
||||
| `internal/agent/core/process.go` | 修改(可能需要):传递 toolsUsed 到 Prune 或加辅助方法 |
|
||||
| `internal/memory/indexer.go` | 修改:`CleanTemplateText` → `CleanText` |
|
||||
| `internal/memory/cut.go` | 修改:同上 |
|
||||
| `internal/plugins/agentcli/plugin.go` | 修改:工具标记 `NoMemory: true` |
|
||||
| `internal/plugins/cmd/plugin.go` | 修改:`cmd_run` 标记 `NoMemory: true` |
|
||||
| `cmd/homed/main.go` | 修改:`memory.SetTextCleaner(pluginReg.CleanText)` |
|
||||
|
||||
---
|
||||
|
||||
## 实施顺序
|
||||
|
||||
1. SDK 本地副本 + 加 `NoMemory`/`RegisterTextCleaner`
|
||||
2. Registry 聚合 cleaners + `CleanText`
|
||||
3. Memory 动态 cleaner + 改名调用者
|
||||
4. StageHost `ToolDef(name)`
|
||||
5. eventloop + context Prune + process 加入 NoMemory 检查
|
||||
6. 标记 agentcli/cmd 工具的 `NoMemory: true`
|
||||
7. main.go 串联
|
||||
8. 删除旧代码(QQ 正则、source 硬编码)
|
||||
9. 构建 + 测试
|
||||
Reference in New Issue
Block a user