agentscope-ai / agentscope-ai/agentscope-java

[Bug]: Lightweight compaction results are discarded when summarization is skipped

未關閉
#2,970 0 則留言 0 個 reaction 已指派 0 人 在 GitHub 檢視
主要語言
Java
星號
5.6k
分支
1.3k
平均合併
4 天 12 小時
30 天內合併 PR
77

描述

## Describe the bug

`ConversationCompactor.compactIfNeeded()` performs argument truncation and tool-result pruning before deciding whether to summarize. Both lightweight passes return a replacement list when they change messages.

However, the method returns `Optional.empty()` in either of these cases:

1. The reduced conversation is below the summarization trigger.
2. The trigger fires, but the safe cutoff is zero because the conversation fits within the preserved tail.

`CompactionMiddleware` interprets the empty result as a no-op and forwards the original `ReasoningInput`. The reduced messages therefore reach neither the immediate model request nor the working `AgentState`. This can leave oversized tool content in subsequent requests even though the lightweight pass successfully reduced it.

## Reproduction

Reproduced on upstream `main` at `ea511ec2f68ca929a6bbc4e92cd0c0952a38c592` (`2.0.3-SNAPSHOT`), using Java 17 and a mocked model; no provider calls are required.

- Build a conversation containing a user message, an assistant tool call, and its matching tool result.
- Put `"x".repeat(10_000)` in the tool result.
- Configure `triggerTokens(1_000)`, `keepTokens(0)`, `keepMessages(20)` and pruning with `protectTokens(0)`, `minimumTokens(1)`, `maxOutputChars(100)`.
- Invoke `CompactionMiddleware.onReasoning()` and capture the input received by `next`.
- The tool result is pruned internally, but `next` receives the original input and state is unchanged.

Setting `triggerMessages(3)` reproduces the second early-return path (zero cutoff). Enabling argument truncation instead of result pruning reproduces both paths with a large `write_file` content argument.

A regression test exercises all four combinations at the real middleware boundary: all four fail on the baseline, while two unchanged-conversation controls pass.

## Expected behavior

Return the reduced conversation whenever a lightweight pass changed it, even when summarization is skipped. Reserve `Optional.empty()` for a genuine no-op. This lets existing callers update both the request and working state without adding an LLM call or changing the public method signature.

## Related work and scope

This is distinct from #2267 / #2319, which fixed `ToolResultEvictionMiddleware` updating state without updating the copied reasoning input. The issue here is the two early returns in `ConversationCompactor`.

This report does not propose changing pruning thresholds, protected windows, default argument truncation settings, or summary-error handling. I am preparing a small fix with regression tests.

貢獻指南

開啟貢獻指南

評估

這個 Issue 還沒有評估資料。

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。