@@ -23,58 +23,86 @@ func microCompactMessages(messages []providertypes.Message) []providertypes.Mess
2323}
2424
2525// microCompactMessagesWithPolicies 按工具策略对裁剪后的消息做只读投影式微压缩。
26+ // 仅对需要压缩的工具消息做深拷贝,其余消息共享原始引用以减少内存分配。
2627func microCompactMessagesWithPolicies (messages []providertypes.Message , policies MicroCompactPolicySource , retainedToolSpans int , summarizers MicroCompactSummarizerSource ) []providertypes.Message {
2728 if retainedToolSpans <= 0 {
2829 retainedToolSpans = defaultMicroCompactRetainedToolSpans
2930 }
3031
31- cloned := cloneContextMessages (messages )
32- if len (cloned ) == 0 {
33- return cloned
32+ if len (messages ) == 0 {
33+ return nil
3434 }
3535
36- spans := internalcompact .BuildMessageSpans (cloned )
36+ spans := internalcompact .BuildMessageSpans (messages )
3737 protectedStart , hasProtectedTail := internalcompact .ProtectedTailStart (spans )
3838 retainedCompactableSpans := 0
3939
40+ modifiedIndices := make (map [int ]struct {})
41+ var pendingCompactions []compactionPending
42+
4043 for spanIndex := len (spans ) - 1 ; spanIndex >= 0 ; spanIndex -- {
4144 span := spans [spanIndex ]
4245 if hasProtectedTail && span .Start >= protectedStart {
4346 continue
4447 }
45- if ! isToolCallSpan (cloned , span ) {
48+ if ! isToolCallSpan (messages , span ) {
4649 continue
4750 }
4851
49- compactableIDs , toolNames := compactableToolCallIDs (cloned [span .Start ].ToolCalls , policies )
52+ compactableIDs , toolNames := compactableToolCallIDs (messages [span .Start ].ToolCalls , policies )
5053 if len (compactableIDs ) == 0 {
5154 continue
5255 }
5356 if retainedCompactableSpans < retainedToolSpans {
54- if hasCompactableToolMessage (cloned , span , compactableIDs ) {
57+ if hasCompactableToolMessage (messages , span , compactableIDs ) {
5558 retainedCompactableSpans ++
5659 }
5760 continue
5861 }
5962
60- compactableContents := compactableToolMessageContents (cloned , span , compactableIDs )
63+ compactableContents := compactableToolMessageContents (messages , span , compactableIDs )
6164 if len (compactableContents ) == 0 {
6265 continue
6366 }
6467
65- for messageIndex := span .Start + 1 ; messageIndex < span .End ; messageIndex ++ {
66- content , ok := compactableContents [messageIndex ]
67- if ! ok {
68- continue
69- }
70- summary := summarizeOrClear (cloned [messageIndex ], content , toolNames , summarizers )
71- cloned [messageIndex ].Parts = []providertypes.ContentPart {providertypes .NewTextPart (summary )}
68+ for messageIndex , content := range compactableContents {
69+ modifiedIndices [messageIndex ] = struct {}{}
70+ pendingCompactions = append (pendingCompactions , compactionPending {
71+ index : messageIndex ,
72+ content : content ,
73+ toolNames : toolNames ,
74+ })
75+ }
76+ }
77+
78+ if len (modifiedIndices ) == 0 {
79+ return append ([]providertypes.Message (nil ), messages ... )
80+ }
81+
82+ cloned := make ([]providertypes.Message , len (messages ))
83+ for i , msg := range messages {
84+ if _ , needsClone := modifiedIndices [i ]; needsClone {
85+ cloned [i ] = cloneSingleMessage (msg )
86+ } else {
87+ cloned [i ] = msg
7288 }
7389 }
7490
91+ for _ , pending := range pendingCompactions {
92+ summary := summarizeOrClear (cloned [pending .index ], pending .content , pending .toolNames , summarizers )
93+ cloned [pending .index ].Parts = []providertypes.ContentPart {providertypes .NewTextPart (summary )}
94+ }
95+
7596 return cloned
7697}
7798
99+ // compactionPending 记录待压缩的消息索引和所需上下文。
100+ type compactionPending struct {
101+ index int
102+ content string
103+ toolNames map [string ]string
104+ }
105+
78106// cloneContextMessages 深拷贝消息切片,避免读时投影污染 runtime 持有的原始会话消息。
79107func cloneContextMessages (messages []providertypes.Message ) []providertypes.Message {
80108 if len (messages ) == 0 {
@@ -83,19 +111,24 @@ func cloneContextMessages(messages []providertypes.Message) []providertypes.Mess
83111
84112 cloned := make ([]providertypes.Message , 0 , len (messages ))
85113 for _ , message := range messages {
86- next := message
87- next .ToolCalls = append ([]providertypes.ToolCall (nil ), message .ToolCalls ... )
88- if len (message .ToolMetadata ) > 0 {
89- next .ToolMetadata = make (map [string ]string , len (message .ToolMetadata ))
90- for key , value := range message .ToolMetadata {
91- next .ToolMetadata [key ] = value
92- }
93- }
94- cloned = append (cloned , next )
114+ cloned = append (cloned , cloneSingleMessage (message ))
95115 }
96116 return cloned
97117}
98118
119+ // cloneSingleMessage 深拷贝单条消息,隔离 ToolCalls 和 ToolMetadata 的底层引用。
120+ func cloneSingleMessage (msg providertypes.Message ) providertypes.Message {
121+ next := msg
122+ next .ToolCalls = append ([]providertypes.ToolCall (nil ), msg .ToolCalls ... )
123+ if len (msg .ToolMetadata ) > 0 {
124+ next .ToolMetadata = make (map [string ]string , len (msg .ToolMetadata ))
125+ for key , value := range msg .ToolMetadata {
126+ next .ToolMetadata [key ] = value
127+ }
128+ }
129+ return next
130+ }
131+
99132// isToolCallSpan 判断当前 span 是否是由 assistant tool call 起始的原子工具块。
100133func isToolCallSpan (messages []providertypes.Message , span internalcompact.MessageSpan ) bool {
101134 if span .Start < 0 || span .Start >= len (messages ) {
0 commit comments