fix: 保留流式输出连续重复字符

- 按 AgentScope 增量协议原样保留普通文本分片

- 仅在终态完整快照中去除已发送前缀

- 补充连续端口字符回归测试
This commit is contained in:
2026-07-23 19:46:07 +08:00
parent fbeece2d89
commit 5b6b2db5d8
2 changed files with 121 additions and 8 deletions

View File

@@ -257,7 +257,7 @@ public class AgentScopeReActRuntime implements AgentRuntime {
AtomicReference<AgentRuntimeEvent> suspendedEvent = new AtomicReference<>();
// 知识库引注。
Map<String, AgentKnowledgeReference> knowledgeReferences = new LinkedHashMap<>();
// 流式输出归一化,防止出现累计快照的重复输出
// 按 AgentScope 增量协议累计正文,并仅在终态快照到达时消除已发送前缀
StreamDeltaNormalizer deltaNormalizer = new StreamDeltaNormalizer();
// 取消输出标记。
AtomicBoolean cancelled = new AtomicBoolean(false);
@@ -998,12 +998,12 @@ public class AgentScopeReActRuntime implements AgentRuntime {
/**
* 将 AgentScope 可能输出的累计快照归一化为增量。
*
* <p>主线路 mapper 要尽量保持 AgentScope 原始顺序,但不同模型或底层适配器可能
* 输出累计文本。该归一化器只修正同一 message/block 的文本增量,不触碰旁路事件。</p>
* <p>当前运行时显式使用 {@code incremental(true)}。普通事件携带新增文本,必须原样保留;
* {@code last=true} 的终态事件才可能携带完整快照,此时只发送尚未输出的尾部。</p>
*/
private static final class StreamDeltaNormalizer {
private final Map<String, String> previousValues = new LinkedHashMap<>();
private final Map<String, StringBuilder> emittedValues = new LinkedHashMap<>();
/**
* 归一化流式事件。
@@ -1030,10 +1030,14 @@ public class AgentScopeReActRuntime implements AgentRuntime {
return;
}
String key = streamKey(event, payloadKey);
String previousText = previousValues.get(key);
previousValues.put(key, currentText);
if (previousText != null && !previousText.isEmpty() && currentText.startsWith(previousText)) {
event.getPayload().put(payloadKey, currentText.substring(previousText.length()));
boolean last = Boolean.TRUE.equals(event.getPayload().get("last"));
if (!last) {
emittedValues.computeIfAbsent(key, ignored -> new StringBuilder()).append(currentText);
return;
}
StringBuilder emitted = emittedValues.remove(key);
if (emitted != null && currentText.startsWith(emitted.toString())) {
event.getPayload().put(payloadKey, currentText.substring(emitted.length()));
}
}