Skip to content

Commit 4fc1a72

Browse files
authored
On demand summarizer for on demand summaries (#25394)
## Description This change introduces new public API `loadSummarizerContainerAndMakeSummary` to load a summarizer client container to produce an on-demand summary before disposing itself. We also add new `ISummaryConfigurationWithSummaryOnRequest` summary config that we check during summarizer initialzation to turn of client election and internal heurstics.
1 parent 2c846ae commit 4fc1a72

File tree

16 files changed

+745
-36
lines changed

16 files changed

+745
-36
lines changed

packages/framework/aqueduct/package.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,14 @@
154154
"typescript": "~5.4.5"
155155
},
156156
"typeValidation": {
157-
"broken": {},
157+
"broken": {
158+
"Interface_BaseContainerRuntimeFactoryProps": {
159+
"backCompat": false
160+
},
161+
"Interface_ContainerRuntimeFactoryWithDefaultDataStoreProps": {
162+
"backCompat": false
163+
}
164+
},
158165
"entrypoint": "legacy"
159166
}
160167
}

packages/framework/aqueduct/src/test/types/validateAqueductPrevious.generated.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ declare type old_as_current_for_Interface_BaseContainerRuntimeFactoryProps = req
247247
* typeValidation.broken:
248248
* "Interface_BaseContainerRuntimeFactoryProps": {"backCompat": false}
249249
*/
250+
// @ts-expect-error compatibility expected to be broken
250251
declare type current_as_old_for_Interface_BaseContainerRuntimeFactoryProps = requireAssignableTo<TypeOnly<current.BaseContainerRuntimeFactoryProps>, TypeOnly<old.BaseContainerRuntimeFactoryProps>>
251252

252253
/*
@@ -265,6 +266,7 @@ declare type old_as_current_for_Interface_ContainerRuntimeFactoryWithDefaultData
265266
* typeValidation.broken:
266267
* "Interface_ContainerRuntimeFactoryWithDefaultDataStoreProps": {"backCompat": false}
267268
*/
269+
// @ts-expect-error compatibility expected to be broken
268270
declare type current_as_old_for_Interface_ContainerRuntimeFactoryWithDefaultDataStoreProps = requireAssignableTo<TypeOnly<current.ContainerRuntimeFactoryWithDefaultDataStoreProps>, TypeOnly<old.ContainerRuntimeFactoryWithDefaultDataStoreProps>>
269271

270272
/*

packages/loader/container-loader/api-report/container-loader.legacy.alpha.api.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ export interface ILoadFrozenContainerFromPendingStateProps extends ILoadExisting
108108
readonly pendingLocalState: string;
109109
}
110110

111+
// @alpha @legacy
112+
export type ILoadSummarizerContainerProps = Omit<ILoadExistingContainerProps, "pendingLocalState">;
113+
111114
// @beta @legacy
112115
export interface IParsedUrl {
113116
id: string;
@@ -178,6 +181,28 @@ export function loadExistingContainer(loadExistingContainerProps: ILoadExistingC
178181
// @alpha @legacy
179182
export function loadFrozenContainerFromPendingState(props: ILoadFrozenContainerFromPendingStateProps): Promise<IContainer>;
180183

184+
// @alpha @legacy
185+
export function loadSummarizerContainerAndMakeSummary(loadSummarizerContainerProps: ILoadSummarizerContainerProps): Promise<LoadSummarizerSummaryResult>;
186+
187+
// @alpha @legacy
188+
export type LoadSummarizerSummaryResult = {
189+
readonly success: true;
190+
readonly summaryResults: OnDemandSummaryResults;
191+
} | {
192+
readonly success: false;
193+
readonly error: IErrorBase;
194+
};
195+
196+
// @alpha @legacy
197+
export interface OnDemandSummaryResults {
198+
readonly summaryInfo: {
199+
readonly stage?: SummaryStage;
200+
readonly handle?: string;
201+
};
202+
readonly summaryOpBroadcasted: boolean;
203+
readonly summarySubmitted: boolean;
204+
}
205+
181206
// @beta @legacy
182207
export type ProtocolHandlerBuilder = (attributes: IDocumentAttributes, snapshot: IQuorumSnapshot, sendProposal: (key: string, value: any) => number) => IProtocolHandler;
183208

@@ -196,6 +221,9 @@ export function rehydrateDetachedContainer(rehydrateDetachedContainerProps: IReh
196221
// @beta @legacy
197222
export function resolveWithLocationRedirectionHandling<T>(api: (request: IRequest) => Promise<T>, request: IRequest, urlResolver: IUrlResolver, logger?: ITelemetryBaseLogger): Promise<T>;
198223

224+
// @alpha @legacy
225+
export type SummaryStage = "base" | "generate" | "upload" | "submit" | "unknown";
226+
199227
// @beta @legacy
200228
export function tryParseCompatibleResolvedUrl(url: string): IParsedUrl | undefined;
201229

packages/loader/container-loader/src/createAndLoadContainerUtils.ts

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type {
99
IFluidCodeDetails,
1010
IContainerPolicies,
1111
} from "@fluidframework/container-definitions/internal";
12+
import { LoaderHeader } from "@fluidframework/container-definitions/internal";
1213
import type {
1314
FluidObject,
1415
IConfigProviderBase,
@@ -20,10 +21,44 @@ import type {
2021
IDocumentServiceFactory,
2122
IUrlResolver,
2223
} from "@fluidframework/driver-definitions/internal";
24+
import { DriverHeader } from "@fluidframework/driver-definitions/internal";
25+
import {
26+
GenericError,
27+
normalizeError,
28+
createChildMonitoringContext,
29+
mixinMonitoringContext,
30+
sessionStorageConfigProvider,
31+
PerformanceEvent,
32+
isFluidError,
33+
} from "@fluidframework/telemetry-utils/internal";
34+
import { v4 as uuid } from "uuid";
2335

36+
import { DebugLogger } from "./debugLogger.js";
2437
import { createFrozenDocumentServiceFactory } from "./frozenServices.js";
2538
import { Loader } from "./loader.js";
39+
import { pkgVersion } from "./packageVersion.js";
2640
import type { ProtocolHandlerBuilder } from "./protocol.js";
41+
import type {
42+
LoadSummarizerSummaryResult,
43+
OnDemandSummaryResults,
44+
SummarizeOnDemandResults,
45+
} from "./summarizerResultTypes.js";
46+
47+
interface OnDemandSummarizeResultsPromises {
48+
readonly summarySubmitted: Promise<SummarizeOnDemandResults["summarySubmitted"]>;
49+
readonly summaryOpBroadcasted: Promise<SummarizeOnDemandResults["summaryOpBroadcasted"]>;
50+
}
51+
52+
interface OnDemandSummarizeOptions {
53+
readonly reason?: string;
54+
readonly retryOnFailure?: boolean;
55+
readonly fullTree?: boolean;
56+
}
57+
58+
interface SummarizerLike {
59+
readonly ISummarizer?: SummarizerLike;
60+
summarizeOnDemand(options: OnDemandSummarizeOptions): OnDemandSummarizeResultsPromises;
61+
}
2762

2863
/**
2964
* Properties necessary for creating and loading a container.
@@ -103,6 +138,15 @@ export interface ILoadExistingContainerProps extends ICreateAndLoadContainerProp
103138
readonly pendingLocalState?: string | undefined;
104139
}
105140

141+
/**
142+
* Props used to load summarizer container.
143+
* @legacy @alpha
144+
*/
145+
export type ILoadSummarizerContainerProps = Omit<
146+
ILoadExistingContainerProps,
147+
"pendingLocalState"
148+
>;
149+
106150
/**
107151
* Props used to create a detached container.
108152
* @legacy @beta
@@ -200,3 +244,133 @@ export async function loadFrozenContainerFromPendingState(
200244
documentServiceFactory: createFrozenDocumentServiceFactory(props.documentServiceFactory),
201245
});
202246
}
247+
248+
/**
249+
* Loads a summarizer container with the required headers, triggers an on-demand summary, and then closes it.
250+
* Returns success/failure and an optional error for host-side handling.
251+
*
252+
* @legacy @alpha
253+
*/
254+
export async function loadSummarizerContainerAndMakeSummary(
255+
loadSummarizerContainerProps: ILoadSummarizerContainerProps,
256+
): Promise<LoadSummarizerSummaryResult> {
257+
const { logger, configProvider, request: originalRequest } = loadSummarizerContainerProps;
258+
const telemetryProps = {
259+
loaderId: uuid(),
260+
loaderVersion: pkgVersion,
261+
};
262+
263+
const subMc = mixinMonitoringContext(
264+
DebugLogger.mixinDebugLogger("fluid:telemetry", logger, {
265+
all: telemetryProps,
266+
}),
267+
sessionStorageConfigProvider.value,
268+
configProvider,
269+
);
270+
const mc = createChildMonitoringContext({
271+
logger: subMc.logger,
272+
namespace: "SummarizerOnDemand",
273+
});
274+
return PerformanceEvent.timedExecAsync(
275+
mc.logger,
276+
{ eventName: "SummarizerOnDemandSummary" },
277+
async (event) => {
278+
const baseHeaders = originalRequest.headers;
279+
const request = {
280+
...originalRequest,
281+
headers: {
282+
...baseHeaders,
283+
[LoaderHeader.cache]: false,
284+
[LoaderHeader.clientDetails]: {
285+
capabilities: { interactive: false },
286+
type: "summarizer",
287+
},
288+
[DriverHeader.summarizingClient]: true,
289+
[LoaderHeader.reconnect]: false,
290+
},
291+
};
292+
293+
const container = await loadExistingContainer({
294+
...loadSummarizerContainerProps,
295+
request,
296+
});
297+
298+
let summarySubmitted: SummarizeOnDemandResults["summarySubmitted"];
299+
let summaryOpBroadcasted: SummarizeOnDemandResults["summaryOpBroadcasted"];
300+
try {
301+
if (container.getEntryPoint === undefined) {
302+
throw new GenericError("container.getEntryPoint() is undefined");
303+
}
304+
const fluidObject = (await container.getEntryPoint()) as FluidObject<SummarizerLike>;
305+
const summarizer = fluidObject?.ISummarizer;
306+
if (summarizer === undefined) {
307+
throw new GenericError("Summarizer entry point not available");
308+
}
309+
// Host controlled feature gate for fullTree
310+
// Default value will be false
311+
const fullTreeGate =
312+
mc.config.getBoolean("Fluid.Summarizer.FullTree.OnDemand") === true;
313+
314+
const summarizeResults: OnDemandSummarizeResultsPromises =
315+
summarizer.summarizeOnDemand({
316+
reason: "summaryOnRequest",
317+
retryOnFailure: true,
318+
fullTree: fullTreeGate,
319+
});
320+
[summarySubmitted, summaryOpBroadcasted] = await Promise.all([
321+
summarizeResults.summarySubmitted,
322+
summarizeResults.summaryOpBroadcasted,
323+
]);
324+
325+
const summaryResults: OnDemandSummaryResults = {
326+
summarySubmitted: summarySubmitted.success,
327+
summaryInfo: summarySubmitted.success
328+
? {
329+
stage: summarySubmitted.data.stage,
330+
handle: summaryOpBroadcasted.success
331+
? summaryOpBroadcasted.data.summarizeOp.contents.handle
332+
: undefined,
333+
}
334+
: {},
335+
summaryOpBroadcasted: summaryOpBroadcasted.success,
336+
};
337+
338+
if (summarySubmitted.success && summaryOpBroadcasted.success) {
339+
event.end({
340+
success: true,
341+
summarySubmitted: true,
342+
summaryOpBroadcasted: true,
343+
});
344+
return {
345+
success: true,
346+
summaryResults,
347+
};
348+
}
349+
350+
const failureError =
351+
summarySubmitted.success === false
352+
? summarySubmitted.error
353+
: summaryOpBroadcasted.success === false
354+
? summaryOpBroadcasted.error
355+
: new GenericError("On demand summary failed");
356+
357+
event.end({
358+
success: false,
359+
summarySubmitted: summarySubmitted.success,
360+
summaryOpBroadcasted: summaryOpBroadcasted.success,
361+
});
362+
return {
363+
success: false,
364+
error: failureError,
365+
};
366+
} catch (error) {
367+
event.cancel({ success: false }, error);
368+
const caughtError = isFluidError(error) ? error : normalizeError(error);
369+
return { success: false, error: caughtError };
370+
} finally {
371+
container.dispose();
372+
}
373+
},
374+
{ start: true, end: true, cancel: "generic" },
375+
);
376+
}

packages/loader/container-loader/src/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,19 @@ export {
1111
loadExistingContainer,
1212
rehydrateDetachedContainer,
1313
loadFrozenContainerFromPendingState,
14+
loadSummarizerContainerAndMakeSummary,
1415
type ICreateAndLoadContainerProps,
1516
type ICreateDetachedContainerProps,
1617
type ILoadExistingContainerProps,
18+
type ILoadSummarizerContainerProps,
1719
type IRehydrateDetachedContainerProps,
1820
type ILoadFrozenContainerFromPendingStateProps,
1921
} from "./createAndLoadContainerUtils.js";
22+
export type {
23+
LoadSummarizerSummaryResult,
24+
OnDemandSummaryResults,
25+
SummaryStage,
26+
} from "./summarizerResultTypes.js";
2027
export {
2128
type ICodeDetailsLoader,
2229
type IFluidModuleWithDetails,

0 commit comments

Comments
 (0)