mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-11-01 06:47:29 +00:00
记忆层: - MemoryBlock 承载 FrameTS/PageSeq/PageKind/BlockRole/CallID - coldcontext 归档与回填按完整页传递,工具调用参数和配对键不丢失 - 召回先估算总 token,超预算时按全局页相似度标记 inactive - inactive 仅是 context 渲染期状态,不落盘、不进入 coldcontext,Prune 后重置 - Peek/确认/Remove 两阶段召回,失败保留冷上下文副本 - stablemem 记录 last_recalled_at,删块连带清理边 命名: - internal/memory/document → internal/memory/coldcontext - Doc/DocEvent/DocHit → ColdDoc/ColdEvent/ColdHit - ContextToColdContext/RecentColdContexts/FindColdContexts - 保留既有 JSON 标签、doc_ 文件前缀与 doc_count 状态键,兼容存量数据 安全治理: - 工具策略门禁、安全配置、事件总线发布者身份与入站脱敏 - 声明式 SecurityAPI 与 QQ owner/guest profile - 日志保留策略与相关 e2e/回归判据 验证:go build ./...、go vet ./...、go test ./...(62 包); 生产副本 e2e:7013 块迁移后 ID/向量/指纹 0 变化、0 缺失。
90 lines
2.9 KiB
Go
90 lines
2.9 KiB
Go
package core
|
||
|
||
import (
|
||
"encoding/json"
|
||
"os"
|
||
"sync"
|
||
"time"
|
||
|
||
agentAPI "github.com/JianFeeeee/HologramAgent/internal/agent/api"
|
||
)
|
||
|
||
// 提示词抓包(opt-in 诊断)。
|
||
//
|
||
// 为什么需要:系统提示词是**每轮动态拼装**的(身份 + 人格 + 记忆 + 时序 +
|
||
// 工具顺序 + 输出规则 + 技能索引 + 首启门禁…),只看代码永远说不清模型
|
||
// 这一轮到底收到了什么。排查「它怎么又列清单了」「为什么它说有某能力」
|
||
// 这类问题时,唯一可靠的事实是**实际发出去的 payload**。
|
||
//
|
||
// 开关是环境变量而不是配置项:它会把记忆与用户输入原样落盘,属于
|
||
// 临时诊断手段,不该在界面上有一个能被随手打开的开关。
|
||
//
|
||
// HOMED_PROMPT_DUMP=/var/log/homeagent/prompt.jsonl
|
||
//
|
||
// 每行一条 JSON:时间、通道、模型、完整 messages(role + content)。
|
||
// 文件权限 0600 —— 内容是用户隐私。
|
||
// promptDump 是 opt-in 的**一次性诊断**手段,不是常驻功能。
|
||
//
|
||
// ## 生命周期约定(2026-10-29 教训)
|
||
//
|
||
// 这个开关原本只为一次排查而加("抓一轮真实提示词,确认 coldcontext
|
||
// 预填充到底注入了什么")。但当时的注释写着"排查完应删除本 drop-in 并删文件",
|
||
// 而**没有人执行那一步**:它在生产上挂了两天,`prompt.jsonl` 涨到 531MB。
|
||
//
|
||
// 这次为此加过"大小上限"(`HOMED_PROMPT_DUMP_MAX_MB`),但那其实是把
|
||
// 临时手段往"长期功能"上推 —— 方向错了。正确做法就是照它自己写明的
|
||
// 处置步骤做掉:删 drop-in、删文件。上限机制已随之移除。
|
||
//
|
||
// ## 若要再用一次
|
||
//
|
||
// 一次性诊断的正确姿势:
|
||
//
|
||
// HOMED_PROMPT_DUMP=/tmp/prompt-$(date +%s).jsonl homed ... # 不要写进 systemd
|
||
// # 看几轮就停,看完立刻删。
|
||
//
|
||
// 不要写进 drop-in —— 那会让"临时"变成"永久",且没有任何人记得删。
|
||
const promptDumpEnv = "HOMED_PROMPT_DUMP"
|
||
|
||
var promptDumpMu sync.Mutex
|
||
|
||
func dumpPrompt(channel string, req *agentAPI.CompletionRequest) {
|
||
path := os.Getenv(promptDumpEnv)
|
||
if path == "" || req == nil {
|
||
return
|
||
}
|
||
entry := map[string]interface{}{
|
||
"at": time.Now().Format(time.RFC3339Nano),
|
||
"channel": channel,
|
||
"model": req.Model,
|
||
}
|
||
msgs := make([]map[string]interface{}, 0, len(req.Messages))
|
||
for _, m := range req.Messages {
|
||
item := map[string]interface{}{"role": m.Role, "content": m.Content}
|
||
if len(m.Blocks) > 0 {
|
||
item["blocks"] = len(m.Blocks)
|
||
}
|
||
if len(m.ToolCalls) > 0 {
|
||
item["tool_calls"] = len(m.ToolCalls)
|
||
}
|
||
msgs = append(msgs, item)
|
||
}
|
||
entry["messages"] = msgs
|
||
entry["tool_count"] = len(req.Tools)
|
||
|
||
line, err := json.Marshal(entry)
|
||
if err != nil {
|
||
return
|
||
}
|
||
line = append(line, '\n')
|
||
|
||
promptDumpMu.Lock()
|
||
defer promptDumpMu.Unlock()
|
||
|
||
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
|
||
if err != nil {
|
||
return
|
||
}
|
||
defer f.Close()
|
||
f.Write(line)
|
||
}
|