mirror of
https://gitcode.com/JianFeeeee/homeagent-sdk.git
synced 2026-10-15 12:15:11 +00:00
docs: 修正全部文档使其与源码实现一致
- Plugin.Start(sdk *PluginSDK) 接口签名改为指针 - 方法表重写: 移除 CallLLM/QueryKnowledge/SetMemory 等不存在方法 - IOInjector 参数顺序修正为 (source, channel, text) - 删除虚构 SDKConfig, 替换为实际 New() 构造函数签名 - .hmap 内容描述一致化 (plugin.so + plugin.dll + main.lua) - 添加 meta/ 包元数据文件
This commit is contained in:
7
example/sanitizer/go.mod
Normal file
7
example/sanitizer/go.mod
Normal file
@ -0,0 +1,7 @@
|
||||
module sanitizer
|
||||
|
||||
go 1.25.0
|
||||
|
||||
require gitcode.com/JianFeeeee/homeagent-sdk v0.0.0-20260708004841-e9bdcf9304b0
|
||||
|
||||
replace gitcode.com/JianFeeeee/homeagent-sdk => ../..
|
||||
11
example/sanitizer/plg.json
Normal file
11
example/sanitizer/plg.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "sanitizer",
|
||||
"name_zh": "输出清洗",
|
||||
"name_en": "sanitizer",
|
||||
"version": "0.1.0",
|
||||
"description": "清洗 LLM 输出中的工具调用残留(思维泄漏)",
|
||||
"author": "HomeAgent SDK",
|
||||
"entry": "plugin.so",
|
||||
"tags": ["sanitizer"],
|
||||
"targets": "linux/amd64"
|
||||
}
|
||||
102
example/sanitizer/plugin.go
Normal file
102
example/sanitizer/plugin.go
Normal file
@ -0,0 +1,102 @@
|
||||
// Package main 是一个外部插件示例(编译为 .so 通过 -buildmode=plugin)。
|
||||
// 在 StagePostAction 阶段清洗 LLM 输出中的工具调用残留(思维泄漏)。
|
||||
//
|
||||
// 编译:
|
||||
//
|
||||
// go build -buildmode=plugin -o sanitizer.so .
|
||||
//
|
||||
// 安装到 HomeAgent 插件目录(如 plugins/sanitizer/plugin.so),
|
||||
// HomeAgent 自动通过 tryLoadSO 加载。
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"gitcode.com/JianFeeeee/homeagent-sdk/sdk"
|
||||
)
|
||||
|
||||
var (
|
||||
toolCallTagRE = regexp.MustCompile(`(?s)<tool_call[^>]*>.*?</tool_call>`)
|
||||
invokeTagRE = regexp.MustCompile(`(?s)<invoke[^>]*>.*?</invoke>`)
|
||||
toolTagRE = regexp.MustCompile(`(?s)<tool[^>]*>.*?</tool>`)
|
||||
functionTagRE = regexp.MustCompile(`(?s)<function[^>]*>.*?</function>`)
|
||||
toolCodeBlockRE = regexp.MustCompile("(?s)```(?:xml|json)?\\s*<tool_call[^>]*>.*?</tool_call>\\s*```")
|
||||
invokeCodeBlockRE = regexp.MustCompile("(?s)```(?:xml|json)?\\s*<invoke[^>]*>.*?</invoke>\\s*```")
|
||||
toolCodeBlockRE2 = regexp.MustCompile("(?s)```(?:xml|json)?\\s*<tool[^>]*>.*?</tool>\\s*```")
|
||||
chineseMarkerRE = regexp.MustCompile(`(?s)【tool_call】.*?【/tool_call】`)
|
||||
multiNewlineRE = regexp.MustCompile(`\n{3,}`)
|
||||
toolNameRE = regexp.MustCompile(`^(cmd_run|terminal_create|terminal_write|memory_|knowledge_|doc_|social_|output_send|output_set_channel|llm_|plgreload|spawn_child|child_result|describe_image|transcribe_audio|ocr_image|timer_set|plugin_install|plugin_remove|qq_|a2a_|mcp_|healthcheck|files_|web_)`)
|
||||
)
|
||||
|
||||
type Plugin struct{}
|
||||
|
||||
func (p *Plugin) Name() string { return "sanitizer" }
|
||||
|
||||
func (p *Plugin) Start(s *sdk.PluginSDK) error {
|
||||
s.SetAutoRestart(true)
|
||||
s.RegisterStage(sdk.StagePostAction, func(ctx *sdk.StageContext) error {
|
||||
ctx.Lock()
|
||||
before := len(ctx.LLMText)
|
||||
ctx.LLMText = cleanToolCallLeakage(ctx.LLMText)
|
||||
after := len(ctx.LLMText)
|
||||
ctx.Unlock()
|
||||
if before != after {
|
||||
log.Printf("[sanitizer] cleaned %d bytes (before=%d after=%d)", before-after, before, after)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
log.Printf("[sanitizer] stage PostAction registered")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Plugin) Stop() error { return nil }
|
||||
|
||||
func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) {
|
||||
return &Plugin{}, nil
|
||||
}
|
||||
|
||||
func cleanToolCallLeakage(content string) string {
|
||||
if content == "" {
|
||||
return content
|
||||
}
|
||||
|
||||
before := len(content)
|
||||
|
||||
content = toolCodeBlockRE.ReplaceAllString(content, "")
|
||||
content = invokeCodeBlockRE.ReplaceAllString(content, "")
|
||||
content = toolCodeBlockRE2.ReplaceAllString(content, "")
|
||||
|
||||
content = toolCallTagRE.ReplaceAllString(content, "")
|
||||
content = invokeTagRE.ReplaceAllString(content, "")
|
||||
content = toolTagRE.ReplaceAllString(content, "")
|
||||
content = functionTagRE.ReplaceAllString(content, "")
|
||||
|
||||
content = chineseMarkerRE.ReplaceAllString(content, "")
|
||||
|
||||
lines := strings.Split(content, "\n")
|
||||
var cleaned []string
|
||||
for _, line := range lines {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if trimmed == "" {
|
||||
cleaned = append(cleaned, line)
|
||||
continue
|
||||
}
|
||||
if toolNameRE.MatchString(trimmed) {
|
||||
if strings.Contains(trimmed, "(") || strings.Contains(trimmed, "\"") || strings.Contains(trimmed, ":") {
|
||||
continue
|
||||
}
|
||||
}
|
||||
cleaned = append(cleaned, line)
|
||||
}
|
||||
content = strings.Join(cleaned, "\n")
|
||||
|
||||
content = multiNewlineRE.ReplaceAllString(content, "\n\n")
|
||||
content = strings.TrimSpace(content)
|
||||
|
||||
if len(content) != before {
|
||||
log.Printf("[sanitizer] cleanToolCallLeakage: %d bytes removed", before-len(content))
|
||||
}
|
||||
return content
|
||||
}
|
||||
31
example/sanitizer/sanitizer_test.go
Normal file
31
example/sanitizer/sanitizer_test.go
Normal file
@ -0,0 +1,31 @@
|
||||
package main
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestCleanToolCallLeakage(t *testing.T) {
|
||||
tests := []struct {
|
||||
name, input, want string
|
||||
}{
|
||||
{"empty", "", ""},
|
||||
{"clean", "你好", "你好"},
|
||||
{"tool_call", "a<tool_call>x</tool_call>b", "ab"},
|
||||
{"invoke", "a<invoke>x</invoke>b", "ab"},
|
||||
{"function", "a<function>x</function>b", "ab"},
|
||||
{"xml_block", "a\n```xml\n<tool_call>x</tool_call>\n```\nb", "a\n\nb"},
|
||||
{"json_block", "a\n```json\n<invoke>x</invoke>\n```\nb", "a\n\nb"},
|
||||
{"bare_code", "```python\nprint(1)\n```", "```python\nprint(1)\n```"},
|
||||
{"chinese_marker", "a【tool_call】x【/tool_call】b", "ab"},
|
||||
{"tool_line", "cmd_run(\"ls\")\nok", "ok"},
|
||||
{"prose_kept", "cmd_run 是一个工具", "cmd_run 是一个工具"},
|
||||
{"multiline", "a\n<tool_call>\nx\n</tool_call>\nb", "a\n\nb"},
|
||||
{"whitespace", "a\n\n\n\nb", "a\n\nb"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := cleanToolCallLeakage(tt.input)
|
||||
if got != tt.want {
|
||||
t.Errorf("got %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user