mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: Fix DevUI streaming memory growth regression (#6038)
* Fix DevUI streaming memory growth regression Bounds retained streaming/debug state in DevUI and strengthens browser regression coverage for long streamed responses. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address DevUI memory review feedback Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix DevUI bundle trailing whitespace Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
e1e6e3d35e
commit
3242d8a4c4
@@ -519,6 +519,7 @@ class ApiClient {
|
||||
lastMessageId,
|
||||
lastSequenceNumber,
|
||||
accumulatedText: storedState?.accumulatedText,
|
||||
accumulatedTextIsPreview: storedState?.accumulatedTextIsPreview,
|
||||
}),
|
||||
event,
|
||||
currentResponseId,
|
||||
|
||||
@@ -15,11 +15,13 @@ export interface StreamingState {
|
||||
lastSequenceNumber: number;
|
||||
timestamp: number; // When this state was last updated
|
||||
completed: boolean; // Whether the stream completed successfully
|
||||
accumulatedText?: string; // Accumulated text content for quick restoration
|
||||
accumulatedText?: string; // Bounded tail preview for refresh restoration
|
||||
accumulatedTextIsPreview?: boolean;
|
||||
}
|
||||
|
||||
const STORAGE_KEY_PREFIX = "devui_streaming_state_";
|
||||
const STATE_EXPIRY_MS = 24 * 60 * 60 * 1000; // 24 hours
|
||||
const MAX_ACCUMULATED_TEXT_PREVIEW_CHARS = 16 * 1024;
|
||||
|
||||
interface CreateStreamingStateOptions {
|
||||
conversationId: string;
|
||||
@@ -27,6 +29,7 @@ interface CreateStreamingStateOptions {
|
||||
lastMessageId?: string;
|
||||
lastSequenceNumber?: number;
|
||||
accumulatedText?: string;
|
||||
accumulatedTextIsPreview?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -36,6 +39,21 @@ function getStorageKey(conversationId: string): string {
|
||||
return `${STORAGE_KEY_PREFIX}${conversationId}`;
|
||||
}
|
||||
|
||||
function normalizeAccumulatedTextPreview(state: StreamingState): StreamingState {
|
||||
if (
|
||||
state.accumulatedText === undefined ||
|
||||
state.accumulatedText.length <= MAX_ACCUMULATED_TEXT_PREVIEW_CHARS
|
||||
) {
|
||||
return state;
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
accumulatedText: state.accumulatedText.slice(-MAX_ACCUMULATED_TEXT_PREVIEW_CHARS),
|
||||
accumulatedTextIsPreview: true,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Read raw streaming state from storage, including completed entries.
|
||||
*/
|
||||
@@ -56,7 +74,7 @@ function readStreamingState(conversationId: string): StreamingState | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
return state;
|
||||
return normalizeAccumulatedTextPreview(state);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -68,8 +86,9 @@ export function createStreamingState({
|
||||
lastMessageId,
|
||||
lastSequenceNumber = -1,
|
||||
accumulatedText,
|
||||
accumulatedTextIsPreview = false,
|
||||
}: CreateStreamingStateOptions): StreamingState {
|
||||
return {
|
||||
return normalizeAccumulatedTextPreview({
|
||||
conversationId,
|
||||
responseId,
|
||||
lastMessageId,
|
||||
@@ -77,7 +96,8 @@ export function createStreamingState({
|
||||
timestamp: Date.now(),
|
||||
completed: false,
|
||||
accumulatedText,
|
||||
};
|
||||
accumulatedTextIsPreview,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,7 +128,15 @@ export function applyStreamingEventToState(
|
||||
typeof event.delta === "string" &&
|
||||
event.delta.length > 0
|
||||
) {
|
||||
nextState.accumulatedText = `${state.accumulatedText ?? ""}${event.delta}`;
|
||||
const accumulatedText = `${state.accumulatedText ?? ""}${event.delta}`;
|
||||
const isPreview =
|
||||
state.accumulatedTextIsPreview ||
|
||||
accumulatedText.length > MAX_ACCUMULATED_TEXT_PREVIEW_CHARS;
|
||||
|
||||
nextState.accumulatedText = isPreview
|
||||
? accumulatedText.slice(-MAX_ACCUMULATED_TEXT_PREVIEW_CHARS)
|
||||
: accumulatedText;
|
||||
nextState.accumulatedTextIsPreview = isPreview;
|
||||
}
|
||||
|
||||
return nextState;
|
||||
@@ -120,7 +148,7 @@ export function applyStreamingEventToState(
|
||||
export function saveStreamingState(state: StreamingState): void {
|
||||
try {
|
||||
const key = getStorageKey(state.conversationId);
|
||||
const data = JSON.stringify(state);
|
||||
const data = JSON.stringify(normalizeAccumulatedTextPreview(state));
|
||||
localStorage.setItem(key, data);
|
||||
} catch (error) {
|
||||
console.error("Failed to save streaming state:", error);
|
||||
@@ -129,7 +157,7 @@ export function saveStreamingState(state: StreamingState): void {
|
||||
clearExpiredStreamingStates();
|
||||
// Try again
|
||||
const key = getStorageKey(state.conversationId);
|
||||
const data = JSON.stringify(state);
|
||||
const data = JSON.stringify(normalizeAccumulatedTextPreview(state));
|
||||
localStorage.setItem(key, data);
|
||||
} catch {
|
||||
console.error("Failed to save streaming state even after cleanup");
|
||||
|
||||
Reference in New Issue
Block a user