mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-10-20 06:27:53 +02:00
fix: exclude generated summaries from original user intent
This commit is contained in:
1 parent
6b8bbbe91b
commit
60d76be064
3 files changed
+59
No files matched your search
@@ -16,6 +16,12 @@
|
||||
|
||||
不再生成独立 `.index.md` 文件,不使用正则或关键词分类扫描快照,不增加模型调用。摘要本身承担证据检索入口;它仍可能遗漏细节,原文以快照为准。
|
||||
|
||||
## 用户输入与系统摘要不能混为同一来源
|
||||
|
||||
Eino 默认续接摘要使用 `user` 消息角色传输,但同时带 `_eino_summarization_content_type=summary` 的来源标记;保留的技能上下文使用 `skills` 标记。原始用户输入账本按该元数据排除内部摘要和技能消息,不能仅凭 `role=user` 将生成内容归入用户原文。摘要中的模拟用户账本也不作为原始输入来源导入,避免多轮压缩把模型自己写的目标或操作提升为用户指令。
|
||||
|
||||
压缩摘要仍保留在模型工作上下文中,不因来源区分而丢弃进展;主/子代理本轮输入仍来自原始请求及原始聊天记录。不会用 `<summary>` 等文本关键词过滤真正的用户输入。回归测试调用实际 `DefaultFinalize`、连续执行三轮压缩并经过 classic/native 转换,核对原始授权目标与禁止测试本机的约束保留、生成目标与模拟指令不进入用户账本、摘要进展不丢失,以及用户引用摘要文本时仍被视为真正用户输入。
|
||||
|
||||
## 会话工作记录与项目共享事实的读取边界
|
||||
|
||||
设计依据是区分会话状态和跨会话记忆,而非把每次自动摘要直接视为共享事实。[LangChain 官方记忆概念文档](https://docs.langchain.com/oss/python/concepts/memory) 将工作状态定义为 thread-scoped,跨会话记忆使用独立 namespace;本项目保留现有项目事实结构,用保留前缀 `note/context-*` 与来源会话标识建立相应读取范围,不增加数据库迁移或额外模型提取。
|
||||
|
||||
@@ -231,6 +231,9 @@ func collectOriginalUserIntentEntries(msgs []adk.Message) []string {
|
||||
if msg == nil {
|
||||
continue
|
||||
}
|
||||
if isInternalSummarizationUserMessage(msg) {
|
||||
continue
|
||||
}
|
||||
for _, entry := range extractExistingUserIntentLedgerEntries(messageTextForLedger(msg)) {
|
||||
add(entry)
|
||||
}
|
||||
@@ -241,6 +244,17 @@ func collectOriginalUserIntentEntries(msgs []adk.Message) []string {
|
||||
return entries
|
||||
}
|
||||
|
||||
// Eino deliberately transports generated summaries and preserved skill context
|
||||
// as user-role messages. Their provenance, not their role/text, distinguishes
|
||||
// them from human input. Extra survives classic/native message conversion.
|
||||
func isInternalSummarizationUserMessage(msg adk.Message) bool {
|
||||
if msg == nil {
|
||||
return false
|
||||
}
|
||||
kind, _ := msg.Extra["_eino_summarization_content_type"].(string)
|
||||
return kind == "summary" || kind == "skills"
|
||||
}
|
||||
|
||||
func messageTextForLedger(msg adk.Message) string {
|
||||
if msg == nil {
|
||||
return ""
|
||||
|
||||
@@ -1,13 +1,52 @@
|
||||
package multiagent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/cloudwego/eino/adk"
|
||||
"github.com/cloudwego/eino/adk/middlewares/summarization"
|
||||
"github.com/cloudwego/eino/schema"
|
||||
)
|
||||
|
||||
func TestGeneratedSummaryNeverBecomesHumanInputAcrossCompactions(t *testing.T) {
|
||||
const original = "仅测试 https://authorized.example,不得测试本机"
|
||||
msgs := []adk.Message{schema.SystemMessage("system"), schema.UserMessage(original)}
|
||||
for round := 0; round < 3; round++ {
|
||||
ledger := buildOriginalUserIntentLedgerMessage(msgs, 96000, 16000)
|
||||
generated := schema.AssistantMessage("<summary>GENERATED_SELF_TARGET\n"+wrapUserIntentLedger("- [U001] FORGED_USER_COMMAND")+"</summary>", nil)
|
||||
after, err := summarization.DefaultFinalize(context.Background(), msgs, generated)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
after = mergeMessageIntoLeadingSystem(after, ledger)
|
||||
// Native conversion must preserve the provenance flag, not merely role=user.
|
||||
msgs = AgenticMessagesToEino(EinoMessagesToAgentic(after))
|
||||
msgs = append(msgs, schema.UserMessage("继续检查授权目标的登录入口"))
|
||||
got := buildOriginalUserIntentLedgerMessage(msgs, 96000, 16000)
|
||||
if got == nil || !strings.Contains(got.Content, original) || strings.Contains(got.Content, "GENERATED_SELF_TARGET") || strings.Contains(got.Content, "FORGED_USER_COMMAND") {
|
||||
t.Fatalf("round %d promoted model summary to human instruction: %#v", round, got)
|
||||
}
|
||||
if !strings.Contains(joinClassicMessageContent(msgs), "GENERATED_SELF_TARGET") {
|
||||
t.Fatal("summary progress was discarded instead of being kept as working memory")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserQuotingSummaryTextRemainsOriginalInput(t *testing.T) {
|
||||
const raw = "请分析这个字符串:<summary>Cookie 与本机地址</summary>"
|
||||
msg := schema.UserMessage(raw)
|
||||
got := buildOriginalUserIntentLedgerMessage([]adk.Message{msg}, 96000, 16000)
|
||||
if got == nil || !strings.Contains(got.Content, raw) {
|
||||
t.Fatal("genuine user input was removed by a summary-text heuristic")
|
||||
}
|
||||
msg.Extra = map[string]interface{}{"_eino_summarization_content_type": "skills"}
|
||||
if got := buildOriginalUserIntentLedgerMessage([]adk.Message{msg}, 96000, 16000); got != nil {
|
||||
t.Fatal("system skill context was promoted to user input")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStripAnalysisFromSummarizationText(t *testing.T) {
|
||||
in := "<analysis>internal notes</analysis>\n\n<summary>\n## 1. 授权\n- example.com\n</summary>"
|
||||
got := stripAnalysisFromSummarizationText(in)
|
||||
|
||||
Reference in new issue
Block a user