Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/prompts/node/agent/simpleSummarizedHistoryPrompt.tsx
13405 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { Chunk, PrioritizedList, PromptElement, PromptElementProps, UserMessage } from '@vscode/prompt-tsx';
7
import type { LanguageModelToolResult } from 'vscode';
8
import { truncate } from '../../../../util/vs/base/common/strings';
9
import { IToolCall, IToolCallRound } from '../../../prompt/common/intents';
10
import { Tag } from '../base/tag';
11
import { ToolResult } from '../panel/toolCalling';
12
import { DefaultOpenAIKeepGoingReminder } from './openai/defaultOpenAIPrompt';
13
import { SummarizedAgentHistoryProps } from './summarizedConversationHistory';
14
15
/**
16
* "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.
17
* We can end up with history too long to summarize normally in a few ways:
18
* - User switched from a model with a larger context window to one with a smaller context window.
19
* - The context window size was changed for a model.
20
* - A previous summarization failed for some reason or was cancelled.
21
* - Switching from ask mode (no summarization) to agent mode.
22
* - Upgrading from an earlier version with no summarization.
23
* - Toggling the summarization setting.
24
*
25
* 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.
26
* Instead, the fallback strategy is basically this:
27
* - Render one UserMessage with a text-based summary of the conversation. Attachments and other large extra context is omitted.
28
* - Very large tool results and arguments are truncated.
29
* - Pack the context window with as much of the history as possible in a PrioritizedList, but give the first user message the highest priority.
30
*
31
* This should let us strike a balance between speed and reliability and summarization fidelity.
32
*/
33
export class SimpleSummarizedHistory extends PromptElement<SummarizedAgentHistoryProps> {
34
override async render() {
35
const historyEntries = this.getEntriesToRender();
36
const firstEntry = historyEntries.at(0);
37
const restEntries = historyEntries.slice(1);
38
39
return <UserMessage priority={this.props.priority}>
40
The 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 />
41
{firstEntry && this.renderEntry(firstEntry, Number.MAX_SAFE_INTEGER)}
42
<PrioritizedList priority={5000} descending={false}>
43
{...restEntries.map(entry => this.renderEntry(entry))}
44
</PrioritizedList>
45
</UserMessage>;
46
}
47
48
private getEntriesToRender(): (IRoundHistoryEntry | string)[] {
49
const entries: (IRoundHistoryEntry | string)[] = [];
50
51
for (const round of Array.from(this.props.promptContext.toolCallRounds ?? []).reverse()) {
52
entries.unshift({ round, results: this.props.promptContext.toolCallResults });
53
if (round.summary) {
54
return entries;
55
}
56
}
57
58
if (this.props.promptContext.query) {
59
entries.unshift(this.props.promptContext.query);
60
}
61
62
for (const turn of Array.from(this.props.promptContext.history ?? []).reverse()) {
63
for (const round of Array.from(turn.rounds).reverse()) {
64
const results = turn.resultMetadata?.toolCallResults;
65
entries.unshift({ round, results });
66
if (round.summary) {
67
return entries;
68
}
69
}
70
entries.unshift(turn.request.message);
71
}
72
73
return entries;
74
}
75
76
private renderEntry(entry: IRoundHistoryEntry | string, priorityOverride?: number) {
77
if (typeof entry === 'string') {
78
return <ChunkTag name='user' priority={priorityOverride}>
79
{entry}
80
</ChunkTag>;
81
}
82
83
if (entry.round.summary) {
84
return <ChunkTag name='conversation-summary' priority={priorityOverride}>
85
{entry.round.summary}
86
{this.props.endpoint.family === 'gpt-4.1' && <Tag name='reminderInstructions'>
87
<DefaultOpenAIKeepGoingReminder />
88
</Tag>}
89
</ChunkTag>;
90
}
91
92
return this.renderRound(entry.round, entry.results ?? {});
93
}
94
95
private renderRound(round: IToolCallRound, results: Record<string, LanguageModelToolResult>) {
96
const asstMsg = round.response ?
97
<ChunkTag name='assistant'>
98
{round.response}
99
</ChunkTag> :
100
<ChunkTag name='assistant' />;
101
return [
102
asstMsg,
103
...round.toolCalls.map(toolCall => this.renderToolCall(toolCall, results[toolCall.id]))
104
];
105
}
106
107
private renderToolCall(toolCall: IToolCall, result: LanguageModelToolResult | undefined) {
108
return <ChunkTag name='tool'>
109
Used tool "{toolCall.name}" with arguments: {truncate(toolCall.arguments, 200)}<br />
110
{result ?
111
<ToolResult content={result.content} truncate={this.props.maxToolResultLength / 2} toolCallId={toolCall.id} sessionId={this.props.promptContext.request?.sessionId} /> :
112
<>Tool result empty</>}
113
</ChunkTag>;
114
}
115
}
116
117
type ChunkTagProps = PromptElementProps<{
118
readonly name: string;
119
readonly attrs?: Record<string, string | undefined | boolean | number>;
120
}>;
121
122
class ChunkTag extends PromptElement<ChunkTagProps> {
123
render() {
124
const { name, children, attrs = {} } = this.props;
125
126
return <Chunk>
127
<Tag name={name} attrs={attrs}>
128
{children}
129
</Tag>
130
</Chunk>;
131
}
132
}
133
134
interface IRoundHistoryEntry {
135
readonly round: IToolCallRound;
136
readonly results?: Record<string, LanguageModelToolResult>;
137
}
138
139