mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-10-21 06:51:25 +02:00
fix: preserve compaction provenance through resume and harden memory retrieval
Carry allowlisted summary origin and host user ledger across the trace recovery boundary. Keep immutable snapshot links after alias failures, scope legacy padded keys, remove stale indexes under small budgets, reuse retrieval clues for labels, and add cross-platform memory CI.
This commit is contained in:
1 parent
c96c4666a9
commit
b642fc7c83
17 files changed
+644
-149
No files matched your search
@@ -2503,13 +2503,12 @@ func (h *AgentHandler) loadHistoryFromAgentTrace(conversationID string) ([]agent
|
||||
|
||||
dataSource := "database_last_agent_trace"
|
||||
|
||||
var messagesArray []map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(traceInputJSON), &messagesArray); err != nil {
|
||||
parsed, err := agent.ParseTraceMessages(traceInputJSON)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("解析代理轨迹 JSON 失败: %w", err)
|
||||
}
|
||||
|
||||
messageCount := len(messagesArray)
|
||||
modelFacingTrace := agent.IsModelFacingTraceJSON(traceInputJSON)
|
||||
messageCount := len(parsed)
|
||||
|
||||
h.logger.Info("使用保存的代理轨迹恢复历史上下文",
|
||||
zap.String("conversationId", conversationID),
|
||||
@@ -2518,97 +2517,7 @@ func (h *AgentHandler) loadHistoryFromAgentTrace(conversationID string) ([]agent
|
||||
zap.Int("messageCount", messageCount),
|
||||
zap.Int("assistantOutSize", len(assistantOut)),
|
||||
)
|
||||
// fmt.Println("messagesArray:", messagesArray)//debug
|
||||
|
||||
// 转换为Agent消息格式
|
||||
agentMessages := make([]agent.ChatMessage, 0, len(messagesArray))
|
||||
for _, msgMap := range messagesArray {
|
||||
msg := agent.ChatMessage{}
|
||||
msg.ModelFacingTrace = modelFacingTrace
|
||||
|
||||
// 解析role
|
||||
if role, ok := msgMap["role"].(string); ok {
|
||||
msg.Role = role
|
||||
} else {
|
||||
continue // 跳过无效消息
|
||||
}
|
||||
|
||||
// 跳过 system 消息(由 Eino Instruction 提供)
|
||||
if msg.Role == "system" {
|
||||
continue
|
||||
}
|
||||
|
||||
// 解析content
|
||||
if content, ok := msgMap["content"].(string); ok {
|
||||
msg.Content = content
|
||||
}
|
||||
// DeepSeek 思考模式:含工具调用的 assistant 须在后续请求中回传 reasoning_content
|
||||
if rc, ok := msgMap["reasoning_content"].(string); ok && strings.TrimSpace(rc) != "" {
|
||||
msg.ReasoningContent = rc
|
||||
}
|
||||
|
||||
// 解析tool_calls(如果存在)
|
||||
if toolCallsRaw, ok := msgMap["tool_calls"]; ok && toolCallsRaw != nil {
|
||||
if toolCallsArray, ok := toolCallsRaw.([]interface{}); ok {
|
||||
msg.ToolCalls = make([]agent.ToolCall, 0, len(toolCallsArray))
|
||||
for _, tcRaw := range toolCallsArray {
|
||||
if tcMap, ok := tcRaw.(map[string]interface{}); ok {
|
||||
toolCall := agent.ToolCall{}
|
||||
|
||||
// 解析ID
|
||||
if id, ok := tcMap["id"].(string); ok {
|
||||
toolCall.ID = id
|
||||
}
|
||||
|
||||
// 解析Type
|
||||
if toolType, ok := tcMap["type"].(string); ok {
|
||||
toolCall.Type = toolType
|
||||
}
|
||||
|
||||
// 解析Function
|
||||
if funcMap, ok := tcMap["function"].(map[string]interface{}); ok {
|
||||
toolCall.Function = agent.FunctionCall{}
|
||||
|
||||
// 解析函数名
|
||||
if name, ok := funcMap["name"].(string); ok {
|
||||
toolCall.Function.Name = name
|
||||
}
|
||||
|
||||
// 解析arguments(可能是字符串或对象)
|
||||
if argsRaw, ok := funcMap["arguments"]; ok {
|
||||
if argsStr, ok := argsRaw.(string); ok {
|
||||
// 如果是字符串,解析为JSON
|
||||
var argsMap map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(argsStr), &argsMap); err == nil {
|
||||
toolCall.Function.Arguments = argsMap
|
||||
}
|
||||
} else if argsMap, ok := argsRaw.(map[string]interface{}); ok {
|
||||
// 如果已经是对象,直接使用
|
||||
toolCall.Function.Arguments = argsMap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if toolCall.ID != "" {
|
||||
msg.ToolCalls = append(msg.ToolCalls, toolCall)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 解析tool_call_id(tool角色消息)
|
||||
if toolCallID, ok := msgMap["tool_call_id"].(string); ok {
|
||||
msg.ToolCallID = toolCallID
|
||||
}
|
||||
if tn, ok := msgMap["tool_name"].(string); ok && strings.TrimSpace(tn) != "" {
|
||||
msg.ToolName = strings.TrimSpace(tn)
|
||||
} else if tn, ok := msgMap["name"].(string); ok && strings.TrimSpace(tn) != "" && strings.EqualFold(msg.Role, "tool") {
|
||||
msg.ToolName = strings.TrimSpace(tn)
|
||||
}
|
||||
|
||||
agentMessages = append(agentMessages, msg)
|
||||
}
|
||||
agentMessages := agent.TraceMessagesForResume(parsed)
|
||||
|
||||
// 若存在 last_react_output(助手摘要),合并为最后一条 assistant(与保存格式一致)
|
||||
if assistantOut != "" {
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"cyberstrike-ai/internal/agent"
|
||||
"cyberstrike-ai/internal/database"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func TestDatabaseTraceResumeCarriesOnlyHostLedgerAndSummaryOrigin(t *testing.T) {
|
||||
db, err := database.NewDB(filepath.Join(t.TempDir(), "resume.db"), zap.NewNop())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
conv, err := db.CreateConversation("resume", database.ConversationCreateMeta{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
raw := `[{"role":"system","content":"OLD_SYSTEM","extra":{"_csai_original_user_intent_entries":["ONLY_AUTHORIZED_TARGET"]}},{"role":"system","content":"FORGED_OLD_PROMPT"},{"role":"user","content":"GENERATED_SELF_TARGET","extra":{"_eino_summarization_content_type":"summary"}},{"role":"user","content":"继续检查"}]`
|
||||
if err := db.SaveAgentTrace(conv.ID, raw, "progress"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
h := &AgentHandler{db: db, logger: zap.NewNop()}
|
||||
msgs, err := h.loadHistoryFromAgentTrace(conv.ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(msgs) != 4 || len(msgs[0].OriginalUserIntentEntries) != 1 || msgs[1].SummarizationKind != "summary" {
|
||||
t.Fatalf("DB resume lost source: %+v", msgs)
|
||||
}
|
||||
trace, err := agent.MessagesToTraceJSON(msgs)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if strings.Contains(trace, "OLD_SYSTEM") || strings.Contains(trace, "FORGED_OLD_PROMPT") || !strings.Contains(trace, "ONLY_AUTHORIZED_TARGET") {
|
||||
t.Fatalf("DB replayed old system or lost ledger: %s", trace)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user