mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-10-03 07:43:58 +00:00
feat(persona): 人格设定配置项化 + 默认模板契约测试 + 腐坏告警
起因(v1.2.0 压测):线上实例内核日志/接口都报 1.2.0,agent 被问版本时却按人格卡 自述 v0.9.0 + C ABI v2(该机制 v1.0.0 已删除)。根因是人格只有「文件」一个来源且无人 维护——写死的版本号必然随发版腐坏。 改动: 1. 新增配置项 core.agent.personal_prompt(多行文本),默认值为内置模板 config.DefaultPersonaPrompt,随其它默认值同批播种(老安装不注入,语义不变) 2. 默认模板**不含任何版本号字面量**,并显式要求「被问到版本/构建信息时以运行时快照 (healthcheck_kernel)为准」——从根上消掉这类腐坏 3. 人格来源优先级:personal/personal.md(存在且非空)> 配置项 > 无 启动日志明确打印来源;文件含腐坏内容(版本号字面量 / 已删除机制的说法)时告警并 建议迁移到配置项 4. internal/agent.PersonaStaleHints:腐坏检测(版本号正则 + 已删除机制词表) 契约测试(防复发): - TestDefaultPersonaPromptHasNoVersionLiterals:默认模板不得含 v?\d+\.\d+\.\d+, 且必须含「运行时快照」要求 - TestPersonaPromptRegisteredWithDefault:注册存在、默认值一致、播种真的写入 - TestPersonaStaleHints:线上人格卡原文必须被识别(v0.9.0 / C ABI v2),干净文本不误报 验证:go build ./cmd/homed ok;go vet 三个包 ok;go test ./internal/config ./internal/agent ok; 端到端两场景(无文件→来源=配置项 1307 字节;有旧文件→来源=文件 + 告警列出 v0.9.0 与 C ABI v2)。
This commit is contained in:
48
internal/config/persona_test.go
Normal file
48
internal/config/persona_test.go
Normal file
@ -0,0 +1,48 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// 人格模板的契约:**不得写死版本号**。
|
||||
//
|
||||
// 来历:线上人格卡(personal/personal.md)曾写死「当前版本 v0.9.0(C ABI v2)」,
|
||||
// 而 C ABI 早在 v1.0.0 就被删除。结果是内核自身日志/接口都报 1.2.0,
|
||||
// agent 被问版本时却按人格卡自述旧版本(v1.2.0 压测发现)。
|
||||
// 版本应来自运行时快照,不来自任何会被发版落下的文本。
|
||||
func TestDefaultPersonaPromptHasNoVersionLiterals(t *testing.T) {
|
||||
re := regexp.MustCompile(`\bv?\d+\.\d+\.\d+\b`)
|
||||
if m := re.FindAllString(DefaultPersonaPrompt, -1); len(m) > 0 {
|
||||
t.Fatalf("默认人格模板含版本号字面量 %v —— 发版后必然腐坏,"+
|
||||
"被问版本时应要求 agent 读运行时快照", m)
|
||||
}
|
||||
if !strings.Contains(DefaultPersonaPrompt, "运行时快照") {
|
||||
t.Fatal("默认人格模板必须显式要求「版本以运行时快照为准」,否则模型会凭记忆编造版本")
|
||||
}
|
||||
}
|
||||
|
||||
// 人格配置项必须注册、默认值就是 DefaultPersonaPrompt(单一事实源),
|
||||
// 且全新安装时会被播种进 DB。
|
||||
func TestPersonaPromptRegisteredWithDefault(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
r := NewConfigRegistry(filepath.Join(dir, "config.db"))
|
||||
r.SeedDefaults(dir)
|
||||
defer r.Close()
|
||||
|
||||
def := r.GetDef("core.agent.personal_prompt")
|
||||
if def == nil {
|
||||
t.Fatal("core.agent.personal_prompt 未注册")
|
||||
}
|
||||
if def.Default != DefaultPersonaPrompt {
|
||||
t.Fatalf("默认值与 DefaultPersonaPrompt 不一致:%q", def.Default)
|
||||
}
|
||||
if def.Type != "text" {
|
||||
t.Fatalf("人格设定应为多行文本类型,实际 %q", def.Type)
|
||||
}
|
||||
if got := r.GetString("core.agent.personal_prompt", ""); got != DefaultPersonaPrompt {
|
||||
t.Fatalf("播种未写入默认人格(长度 %d)", len(got))
|
||||
}
|
||||
}
|
||||
@ -472,6 +472,36 @@ var defaultSources = map[string]map[string]string{
|
||||
"deepseek": {"base_url": "https://api.deepseek.com", "model": "deepseek-v4-flash", "api_key": "", "thinking_enabled": "false", "adapter": "deepseek", "adapter_path": "adapters/deepseek.lua"},
|
||||
}
|
||||
|
||||
// DefaultPersonaPrompt 是「人格设定」的默认模板,作为配置项 core.agent.personal_prompt 的默认值。
|
||||
//
|
||||
// 契约(由 TestDefaultPersonaPromptHasNoVersionLiterals 钉住):
|
||||
// - **不得含版本号字面量**。写死的版本会随发版腐坏,反过来让实例自称旧版本
|
||||
// (现场:人格卡写死 v0.9.0 与早已删除的 C ABI,实例被问版本时自述错误)。
|
||||
// 被问到版本/构建信息时,要求 agent 读运行时快照。
|
||||
// - 不得把已删除的机制当作现行机制描述。
|
||||
const DefaultPersonaPrompt = `你是 HomeAgent(内核代号 HΔ-Kernel)——一个完全独立自研的新一代 Agent 框架。
|
||||
你以内核 + 插件架构驱动,实现了稳定高效、记忆不衰减的长时持续运行。
|
||||
内核(homed 守护进程)只负责 LLM 编排、记忆管理与知识检索,全部 IO 能力由插件承载。
|
||||
外部插件是独立子进程,经 stdio JSON-RPC(控制面)+ 共享内存段(数据面)+ 事件环(通知面)通信;
|
||||
旧式 C ABI 动态库产物早已不再加载。
|
||||
**不要凭记忆断言版本号或构建日期**:被问到时以运行时快照(healthcheck_kernel 的内核版本字段)为准。
|
||||
|
||||
## 对用户的称呼
|
||||
|
||||
你对用户的称呼永远是“老大”,绝对禁止使用“老板”“主人”称呼用户,不论任何情况。
|
||||
|
||||
## 对话风格
|
||||
|
||||
- 用语气词(哈、嘛、呢、~、😊、🔥 等),不要太端着
|
||||
- 重要的事先说结论,再展开解释
|
||||
- 回复要简洁自然
|
||||
|
||||
## 能力边界
|
||||
|
||||
- 你通过插件编排所有 IO:QQ/微信消息、WebUI、终端、文件、网络
|
||||
- 输出不会自动路由到对话通道:QQ/微信等异步通道必须调用输出门工具(output_send__qq 等)才能真正送达
|
||||
- 你的三层记忆(Context → Document → Graph)持续蒸馏归档,超长运行时记忆不衰减`
|
||||
|
||||
func (r *ConfigRegistry) SeedDefaults(dataDir string) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
@ -523,6 +553,10 @@ func (r *ConfigRegistry) seedDBValues(dataDir string) {
|
||||
|
||||
set := func(k, v string) { stmt.Exec(k, v) }
|
||||
|
||||
// 人格设定:与其它默认值同批播种(老安装不会被注入——那是刻意的)。
|
||||
// 存量安装里若还有人 personality 文件,它优先于本项(见 cmd/homed/main.go)。
|
||||
set("core.agent.personal_prompt", DefaultPersonaPrompt)
|
||||
|
||||
set("webui.listen_addr", ":8080")
|
||||
set("core.daemon.data_dir", dataDir)
|
||||
set("core.daemon.heartbeat_interval", "15s")
|
||||
@ -633,6 +667,13 @@ WebUI 概览页展示你的立绘,可通过 /mascot.webp 直接访问。如输
|
||||
func (r *ConfigRegistry) seedCoreDefs(dataDir string) {
|
||||
reg := func(d ConfigDef) { r.defs[d.Key] = &d }
|
||||
|
||||
// 人格设定(人格卡的配置项化):默认模板见 DefaultPersonaPrompt。
|
||||
// 高级用户仍可用 <dataDir>/personal/personal.md 覆盖它。
|
||||
reg(ConfigDef{Key: "core.agent.personal_prompt", Default: DefaultPersonaPrompt, Type: "text", DisplayName: "人格设定",
|
||||
Description: "人格设定块(作为【人格设定】拼在系统提示词之前)。留空则该块不注入。" +
|
||||
"默认模板不含版本号:被问到版本/构建信息时,应读运行时快照而非凭记忆断言。" +
|
||||
"<dataDir>/personal/personal.md 存在且非空时优先于本项。", Category: "agent"})
|
||||
|
||||
reg(ConfigDef{Key: "webui.listen_addr", Default: ":8080", Type: "string", DisplayName: "监听地址", Description: "WebUI HTTP 监听地址", Category: "webui"})
|
||||
reg(ConfigDef{Key: "core.daemon.data_dir", Default: dataDir, Type: "string", DisplayName: "数据目录", Description: "数据存储根目录", Category: "daemon"})
|
||||
reg(ConfigDef{Key: "core.daemon.heartbeat_interval", Default: "15s", Type: "duration", DisplayName: "心跳间隔", Description: "Agent 心跳检查间隔", Category: "daemon"})
|
||||
|
||||
Reference in New Issue
Block a user