Path: blob/main/extensions/copilot/src/extension/prompts/node/agent/simpleSummarizedHistoryPrompt.tsx
13405 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { Chunk, PrioritizedList, PromptElement, PromptElementProps, UserMessage } from '@vscode/prompt-tsx';6import type { LanguageModelToolResult } from 'vscode';7import { truncate } from '../../../../util/vs/base/common/strings';8import { IToolCall, IToolCallRound } from '../../../prompt/common/intents';9import { Tag } from '../base/tag';10import { ToolResult } from '../panel/toolCalling';11import { DefaultOpenAIKeepGoingReminder } from './openai/defaultOpenAIPrompt';12import { SummarizedAgentHistoryProps } from './summarizedConversationHistory';1314/**15* "SimpleSummarizedHistory" is a fallback for when the main history summarization fails, either due to the conversation history being longer than the context window, or some other reason.16* We can end up with history too long to summarize normally in a few ways:17* - User switched from a model with a larger context window to one with a smaller context window.18* - The context window size was changed for a model.19* - A previous summarization failed for some reason or was cancelled.20* - Switching from ask mode (no summarization) to agent mode.21* - Upgrading from an earlier version with no summarization.22* - Toggling the summarization setting.23*24* We could deal with this by summarizing recursively over context-window-sized chunks, but I don't want to make the user wait for multiple rounds of summarization.25* Instead, the fallback strategy is basically this:26* - Render one UserMessage with a text-based summary of the conversation. Attachments and other large extra context is omitted.27* - Very large tool results and arguments are truncated.28* - Pack the context window with as much of the history as possible in a PrioritizedList, but give the first user message the highest priority.29*30* This should let us strike a balance between speed and reliability and summarization fidelity.31*/32export class SimpleSummarizedHistory extends PromptElement<SummarizedAgentHistoryProps> {33override async render() {34const historyEntries = this.getEntriesToRender();35const firstEntry = historyEntries.at(0);36const restEntries = historyEntries.slice(1);3738return <UserMessage priority={this.props.priority}>39The following is a compressed version of the preceeding history in the current conversation. The first message is kept, some history may be truncated after that:<br />40{firstEntry && this.renderEntry(firstEntry, Number.MAX_SAFE_INTEGER)}41<PrioritizedList priority={5000} descending={false}>42{...restEntries.map(entry => this.renderEntry(entry))}43</PrioritizedList>44</UserMessage>;45}4647private getEntriesToRender(): (IRoundHistoryEntry | string)[] {48const entries: (IRoundHistoryEntry | string)[] = [];4950for (const round of Array.from(this.props.promptContext.toolCallRounds ?? []).reverse()) {51entries.unshift({ round, results: this.props.promptContext.toolCallResults });52if (round.summary) {53return entries;54}55}5657if (this.props.promptContext.query) {58entries.unshift(this.props.promptContext.query);59}6061for (const turn of Array.from(this.props.promptContext.history ?? []).reverse()) {62for (const round of Array.from(turn.rounds).reverse()) {63const results = turn.resultMetadata?.toolCallResults;64entries.unshift({ round, results });65if (round.summary) {66return entries;67}68}69entries.unshift(turn.request.message);70}7172return entries;73}7475private renderEntry(entry: IRoundHistoryEntry | string, priorityOverride?: number) {76if (typeof entry === 'string') {77return <ChunkTag name='user' priority={priorityOverride}>78{entry}79</ChunkTag>;80}8182if (entry.round.summary) {83return <ChunkTag name='conversation-summary' priority={priorityOverride}>84{entry.round.summary}85{this.props.endpoint.family === 'gpt-4.1' && <Tag name='reminderInstructions'>86<DefaultOpenAIKeepGoingReminder />87</Tag>}88</ChunkTag>;89}9091return this.renderRound(entry.round, entry.results ?? {});92}9394private renderRound(round: IToolCallRound, results: Record<string, LanguageModelToolResult>) {95const asstMsg = round.response ?96<ChunkTag name='assistant'>97{round.response}98</ChunkTag> :99<ChunkTag name='assistant' />;100return [101asstMsg,102...round.toolCalls.map(toolCall => this.renderToolCall(toolCall, results[toolCall.id]))103];104}105106private renderToolCall(toolCall: IToolCall, result: LanguageModelToolResult | undefined) {107return <ChunkTag name='tool'>108Used tool "{toolCall.name}" with arguments: {truncate(toolCall.arguments, 200)}<br />109{result ?110<ToolResult content={result.content} truncate={this.props.maxToolResultLength / 2} toolCallId={toolCall.id} sessionId={this.props.promptContext.request?.sessionId} /> :111<>Tool result empty</>}112</ChunkTag>;113}114}115116type ChunkTagProps = PromptElementProps<{117readonly name: string;118readonly attrs?: Record<string, string | undefined | boolean | number>;119}>;120121class ChunkTag extends PromptElement<ChunkTagProps> {122render() {123const { name, children, attrs = {} } = this.props;124125return <Chunk>126<Tag name={name} attrs={attrs}>127{children}128</Tag>129</Chunk>;130}131}132133interface IRoundHistoryEntry {134readonly round: IToolCallRound;135readonly results?: Record<string, LanguageModelToolResult>;136}137138139