mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-27 12:53:35 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 02600a3b90 | |||
| 02861f5d64 | |||
| cea8011f3d |
32
internal/agent/core/promptvars_test.go
Normal file
32
internal/agent/core/promptvars_test.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gitcode.com/JianFeeeee/HomeAgent/internal/meta"
|
||||||
|
sdkmeta "gitcode.com/JianFeeeee/homeagent-sdk/meta"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestExpandPromptVars(t *testing.T) {
|
||||||
|
got := expandPromptVars("型号 {{kernel_version}}({{kernel_commit}}),SDK {{sdk_version}}")
|
||||||
|
if !strings.Contains(got, meta.Version) || !strings.Contains(got, sdkmeta.Version) {
|
||||||
|
t.Fatalf("占位符未展开: %q", got)
|
||||||
|
}
|
||||||
|
if strings.Contains(got, "{{") {
|
||||||
|
t.Fatalf("仍有未展开的内置占位符: %q", got)
|
||||||
|
}
|
||||||
|
// 人格卡实测原文:写死了 v1.0.3,应能被占位符取代
|
||||||
|
live := expandPromptVars("你是 HomeAgent 的看板娘「小宅」(Xiao Zhai),HΔ-Kernel v{{kernel_version}} 型号的家政型 AI 管家助手。")
|
||||||
|
if strings.Contains(live, "1.0.3") || !strings.Contains(live, "v"+meta.Version) {
|
||||||
|
t.Fatalf("人格卡版本未跟随内核: %q", live)
|
||||||
|
}
|
||||||
|
// 未知占位符必须原样保留(写错要看得见,不能被静默吞掉)
|
||||||
|
if unk := expandPromptVars("版本 {{kernel_verison}}"); !strings.Contains(unk, "{{kernel_verison}}") {
|
||||||
|
t.Fatalf("未知占位符被吞: %q", unk)
|
||||||
|
}
|
||||||
|
// 无占位符时原样返回(人格卡热路径,不做无谓拷贝)
|
||||||
|
if plain := "无占位符"; expandPromptVars(plain) != plain {
|
||||||
|
t.Fatal("无占位符时不应改写")
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,6 +5,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
agentIO "gitcode.com/JianFeeeee/HomeAgent/internal/agent/io"
|
agentIO "gitcode.com/JianFeeeee/HomeAgent/internal/agent/io"
|
||||||
|
"gitcode.com/JianFeeeee/HomeAgent/internal/meta"
|
||||||
|
sdkmeta "gitcode.com/JianFeeeee/homeagent-sdk/meta"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *Agent) buildMemoryContext(input string, maxTokens int) string {
|
func (a *Agent) buildMemoryContext(input string, maxTokens int) string {
|
||||||
@ -37,8 +39,30 @@ func (a *Agent) buildMemoryContext(input string, maxTokens int) string {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// expandPromptVars 展开自定义提示词(人格卡)里的版本占位符。
|
||||||
|
//
|
||||||
|
// 为什么需要:人格卡是**配置项**,一旦写死版本号就会随内核发版而说谎 ——
|
||||||
|
// 实测线上人格卡写着 "HΔ-Kernel v1.0.3 型号",内核早已 1.3.x,agent 向用户
|
||||||
|
// 自报版本时就照抄 1.0.3。占位符让这类文本永远跟随真实构建:
|
||||||
|
//
|
||||||
|
// {{kernel_version}} → 内核版本(如 1.3.5)
|
||||||
|
// {{kernel_commit}} → 构建 commit
|
||||||
|
// {{sdk_version}} → 所兼容的 SDK 版本(如 1.3.0)
|
||||||
|
//
|
||||||
|
// 未知占位符**原样保留**:写错了要看得见,而不是被静默换成空串。
|
||||||
|
func expandPromptVars(s string) string {
|
||||||
|
if !strings.Contains(s, "{{") {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return strings.NewReplacer(
|
||||||
|
"{{kernel_version}}", meta.Version,
|
||||||
|
"{{kernel_commit}}", meta.Commit,
|
||||||
|
"{{sdk_version}}", sdkmeta.Version,
|
||||||
|
).Replace(s)
|
||||||
|
}
|
||||||
|
|
||||||
func (a *Agent) buildSystemPrompt(memContext string, userInput string) string {
|
func (a *Agent) buildSystemPrompt(memContext string, userInput string) string {
|
||||||
prompt := a.systemPrompt
|
prompt := expandPromptVars(a.systemPrompt)
|
||||||
if prompt == "" {
|
if prompt == "" {
|
||||||
prompt = "你是小宅,HomeAgent 的看板娘,一个家政型 AI 管家助手。绝不用 Unicode emoji,只用颜文字表达情感,句尾带语气词。WebUI 概览页展示你的立绘。"
|
prompt = "你是小宅,HomeAgent 的看板娘,一个家政型 AI 管家助手。绝不用 Unicode emoji,只用颜文字表达情感,句尾带语气词。WebUI 概览页展示你的立绘。"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -742,7 +742,7 @@ func (r *ConfigRegistry) seedCoreDefs(dataDir string) {
|
|||||||
reg(ConfigDef{Key: "core.agent.workdir", Default: "", Type: "string", DisplayName: "工作目录", Description: "Agent 命令执行的默认工作目录(如 cmd_run 工具的 fallback),留空使用内核所在目录", Category: "agent"})
|
reg(ConfigDef{Key: "core.agent.workdir", Default: "", Type: "string", DisplayName: "工作目录", Description: "Agent 命令执行的默认工作目录(如 cmd_run 工具的 fallback),留空使用内核所在目录", Category: "agent"})
|
||||||
reg(ConfigDef{Key: "core.agent.embedding_model_path", Default: "", Type: "string", DisplayName: "预训练词嵌入模型路径", Description: "预训练词嵌入模型路径(word2vec 文本格式),支持逗号分隔多个模型。路径后可加 #topN 规格只加载前 N 个词向量(如 /data/cc.zh.300.vec#top50000)以控制常驻内存,词频降序命中覆盖绝大部分文本。空则使用 TF-IDF 回退。修改后需重启生效。", Category: "agent"})
|
reg(ConfigDef{Key: "core.agent.embedding_model_path", Default: "", Type: "string", DisplayName: "预训练词嵌入模型路径", Description: "预训练词嵌入模型路径(word2vec 文本格式),支持逗号分隔多个模型。路径后可加 #topN 规格只加载前 N 个词向量(如 /data/cc.zh.300.vec#top50000)以控制常驻内存,词频降序命中覆盖绝大部分文本。空则使用 TF-IDF 回退。修改后需重启生效。", Category: "agent"})
|
||||||
reg(ConfigDef{Key: "core.agent.onnx_model_path", Default: "", Type: "string", DisplayName: "ONNX 模型路径", Description: "依存句法分析 ONNX 模型文件路径。留空使用二进制内嵌模型/规则引擎。修改后需重启生效。", Category: "agent"})
|
reg(ConfigDef{Key: "core.agent.onnx_model_path", Default: "", Type: "string", DisplayName: "ONNX 模型路径", Description: "依存句法分析 ONNX 模型文件路径。留空使用二进制内嵌模型/规则引擎。修改后需重启生效。", Category: "agent"})
|
||||||
reg(ConfigDef{Key: "core.agent.system_prompt", Default: "", Type: "text", DisplayName: "系统身份提示词", Description: "Agent 的系统提示词,定义身份和行为规则。留空则使用编译时内置默认值。修改后需重启生效。", Category: "agent"})
|
reg(ConfigDef{Key: "core.agent.system_prompt", Default: "", Type: "text", DisplayName: "系统身份提示词", Description: "Agent 的系统提示词,定义身份和行为规则。留空则使用编译时内置默认值。支持版本占位符(随构建实时展开,避免写死版本号随发版说谎):{{kernel_version}}、{{kernel_commit}}、{{sdk_version}}。修改后需重启生效。", Category: "agent"})
|
||||||
|
|
||||||
reg(ConfigDef{Key: "core.input_processing.image.fallback_provider", Default: "", Type: "string", DisplayName: "图片回退提供商", Description: "当主 LLM 不支持图片处理时使用的提供商(留空则自动降级为文字描述)", Category: "input"})
|
reg(ConfigDef{Key: "core.input_processing.image.fallback_provider", Default: "", Type: "string", DisplayName: "图片回退提供商", Description: "当主 LLM 不支持图片处理时使用的提供商(留空则自动降级为文字描述)", Category: "input"})
|
||||||
reg(ConfigDef{Key: "core.input_processing.image.fallback_model", Default: "", Type: "string", DisplayName: "图片回退模型", Description: "图片回退提供商使用的模型名", Category: "input"})
|
reg(ConfigDef{Key: "core.input_processing.image.fallback_model", Default: "", Type: "string", DisplayName: "图片回退模型", Description: "图片回退提供商使用的模型名", Category: "input"})
|
||||||
|
|||||||
@ -28,7 +28,10 @@ var (
|
|||||||
//
|
//
|
||||||
// ❗main 上此值始终是**下一个未发布中版本**,不随 patch 发布变动
|
// ❗main 上此值始终是**下一个未发布中版本**,不随 patch 发布变动
|
||||||
//(见 docs/git-branching.md §2.1);已发布的版本号看对应的 release/vX.Y.x 与 tag。
|
//(见 docs/git-branching.md §2.1);已发布的版本号看对应的 release/vX.Y.x 与 tag。
|
||||||
Version = "1.3.0"
|
// 1.3.5:系统提示词(人格卡)支持版本占位符 —— 人格卡是配置项,写死版本号
|
||||||
|
// 会随发版说谎(线上写 v1.0.3、内核 1.3.x,agent 就自报 1.0.3)。
|
||||||
|
// 支持 {{kernel_version}} / {{kernel_commit}} / {{sdk_version}}。
|
||||||
|
Version = "1.3.5"
|
||||||
|
|
||||||
// Commit 是构建时的 Git commit hash。
|
// Commit 是构建时的 Git commit hash。
|
||||||
Commit = "unknown"
|
Commit = "unknown"
|
||||||
|
|||||||
@ -138,28 +138,39 @@ func (p *Plugin) Stop() error {
|
|||||||
func (p *Plugin) registerTools(s *sdk.PluginSDK) {
|
func (p *Plugin) registerTools(s *sdk.PluginSDK) {
|
||||||
s.RegisterTool("plugin_install", sdk.ToolDef{
|
s.RegisterTool("plugin_install", sdk.ToolDef{
|
||||||
Name: "plugin_install",
|
Name: "plugin_install",
|
||||||
Description: "从 URL 安装 HomeAgent 插件包(.hmap 文件)。插件已存在时传 overwrite=true 原地更新(升级/降级/重装,保留配置表,无需卸载重装)。更新后需调用 plgreload 或重启生效。",
|
Description: "安装 HomeAgent 插件包(.hmap)。两种来源:url(http/https 下载)或 path(本机路径,配合 plugindev_build 的产物用这个)。插件已存在时传 overwrite=true 原地更新(升级/降级/重装,保留配置表,无需卸载重装)。更新后需调用 plgreload 或重启生效。",
|
||||||
Parameters: map[string]interface{}{
|
Parameters: map[string]interface{}{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": map[string]interface{}{
|
"properties": map[string]interface{}{
|
||||||
"url": map[string]interface{}{
|
"url": map[string]interface{}{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "插件包的下载 URL",
|
"description": "插件包的下载 URL(http/https)",
|
||||||
|
},
|
||||||
|
"path": map[string]interface{}{
|
||||||
|
"type": "string",
|
||||||
|
"description": "插件包在**本机**的路径(.hmap)。与 url 二选一;同时给出时以 path 为准",
|
||||||
},
|
},
|
||||||
"overwrite": map[string]interface{}{
|
"overwrite": map[string]interface{}{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "已存在时原地更新(保留配置)。默认 false",
|
"description": "已存在时原地更新(保留配置)。默认 false",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"required": []string{"url"},
|
|
||||||
},
|
},
|
||||||
}, func(args map[string]interface{}) (interface{}, error) {
|
}, func(args map[string]interface{}) (interface{}, error) {
|
||||||
url, _ := args["url"].(string)
|
|
||||||
if url == "" {
|
|
||||||
return map[string]interface{}{"error": "url is required"}, nil
|
|
||||||
}
|
|
||||||
overwrite, _ := args["overwrite"].(bool)
|
overwrite, _ := args["overwrite"].(bool)
|
||||||
return p.installFromURL(url, overwrite)
|
// path 优先:它对应"agent 自己构建出产物再装"的场景(plugindev_build → plugin_install)。
|
||||||
|
if path, _ := args["path"].(string); strings.TrimSpace(path) != "" {
|
||||||
|
pth := strings.TrimSpace(path)
|
||||||
|
if st, err := os.Stat(pth); err != nil || st.IsDir() {
|
||||||
|
return map[string]interface{}{"error": fmt.Sprintf("path 无效(必须是存在的 .hmap 文件): %s", pth)}, nil
|
||||||
|
}
|
||||||
|
return p.installFromPath(pth, overwrite)
|
||||||
|
}
|
||||||
|
url, _ := args["url"].(string)
|
||||||
|
if strings.TrimSpace(url) == "" {
|
||||||
|
return map[string]interface{}{"error": "需要 url 或 path(二选一)"}, nil
|
||||||
|
}
|
||||||
|
return p.installFromURL(strings.TrimSpace(url), overwrite)
|
||||||
})
|
})
|
||||||
|
|
||||||
s.RegisterTool("plugin_list", sdk.ToolDef{
|
s.RegisterTool("plugin_list", sdk.ToolDef{
|
||||||
@ -455,12 +466,12 @@ func (p *Plugin) installFromData(data []byte, overwrite bool) (interface{}, erro
|
|||||||
|
|
||||||
if existing && !overwrite {
|
if existing && !overwrite {
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
"error": "plugin already exists",
|
"error": "plugin already exists",
|
||||||
"name": pkg.Name,
|
"name": pkg.Name,
|
||||||
"version": pkg.Version,
|
"version": pkg.Version,
|
||||||
"current": oldVersion,
|
"current": oldVersion,
|
||||||
"action": "remove_first",
|
"action": "remove_first",
|
||||||
"hint": `传 "overwrite": true 可原地更新(保留配置)`,
|
"hint": `传 "overwrite": true 可原地更新(保留配置)`,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -475,7 +486,7 @@ func (p *Plugin) installFromData(data []byte, overwrite bool) (interface{}, erro
|
|||||||
os.RemoveAll(backup)
|
os.RemoveAll(backup)
|
||||||
if err := os.Rename(target, backup); err != nil {
|
if err := os.Rename(target, backup); err != nil {
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
"error": "backup old plugin dir failed",
|
"error": "backup old plugin dir failed",
|
||||||
"details": err.Error(),
|
"details": err.Error(),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@ -484,8 +495,8 @@ func (p *Plugin) installFromData(data []byte, overwrite bool) (interface{}, erro
|
|||||||
os.RemoveAll(target)
|
os.RemoveAll(target)
|
||||||
if rbErr := os.Rename(backup, target); rbErr != nil {
|
if rbErr := os.Rename(backup, target); rbErr != nil {
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
"error": "extract failed AND rollback failed",
|
"error": "extract failed AND rollback failed",
|
||||||
"details": err.Error(),
|
"details": err.Error(),
|
||||||
"rollback": rbErr.Error(),
|
"rollback": rbErr.Error(),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@ -507,15 +518,15 @@ func (p *Plugin) installFromData(data []byte, overwrite bool) (interface{}, erro
|
|||||||
action = "reinstalled"
|
action = "reinstalled"
|
||||||
}
|
}
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
"status": "installed",
|
"status": "installed",
|
||||||
"name": pkg.Name,
|
"name": pkg.Name,
|
||||||
"version": pkg.Version,
|
"version": pkg.Version,
|
||||||
"previous_version": oldVersion,
|
"previous_version": oldVersion,
|
||||||
"entry": pkg.Entry,
|
"entry": pkg.Entry,
|
||||||
"checksum": checksum,
|
"checksum": checksum,
|
||||||
"action": action,
|
"action": action,
|
||||||
"reload_required": true,
|
"reload_required": true,
|
||||||
"config_kept": true,
|
"config_kept": true,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user