Skip to content

Commit f97736e

Browse files
committed
Show warning message when the most recent user input gets pruned
First, I integrated llm/compileChat to precompile chat messages before invoking llm/streamChat. The llm/compileChat function returns both the compiled messages and a boolean flag indicating whether the most recent user input has been pruned. This allows us to trigger a warning to notify users when pruning occurs at the last message. The messageOptions is introduced to streamChat as an optional parameter. If we pass messageOptions and the precompiled attribute is set to true, streamChat will skip the process of compiling chat messages, ensuring that the messages won't go through the pruning process twice.
1 parent 8acc20a commit f97736e

File tree

11 files changed

+122
-10
lines changed

11 files changed

+122
-10
lines changed

core/core.ts

+12
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,18 @@ export class Core {
474474
}
475475
});
476476

477+
on("llm/compileChat", async (msg) => {
478+
const { title: modelName, messages, options } = msg.data;
479+
const model = await this.configHandler.llmFromTitle(modelName);
480+
481+
const { compiledChatMessages, lastMessageTruncated } =
482+
model.compileChatMessages(options, messages);
483+
return {
484+
compiledChatMessages,
485+
lastMessageTruncated,
486+
};
487+
});
488+
477489
on("llm/streamChat", (msg) =>
478490
llmStreamChat(
479491
this.configHandler,

core/index.d.ts

+10
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ export interface ILLM extends LLMOptions {
132132
messages: ChatMessage[],
133133
signal: AbortSignal,
134134
options?: LLMFullCompletionOptions,
135+
messageOptions?: MessageOptions,
135136
): AsyncGenerator<ChatMessage, PromptLog>;
136137

137138
chat(
@@ -140,6 +141,11 @@ export interface ILLM extends LLMOptions {
140141
options?: LLMFullCompletionOptions,
141142
): Promise<ChatMessage>;
142143

144+
compileChatMessages(
145+
options: LLMFullCompletionOptions,
146+
messages: ChatMessage[],
147+
): { compiledChatMessages: ChatMEssage[]; lastMessageTruncated: boolean };
148+
143149
embed(chunks: string[]): Promise<number[][]>;
144150

145151
rerank(query: string, chunks: Chunk[]): Promise<number[]>;
@@ -1388,3 +1394,7 @@ export interface TerminalOptions {
13881394
reuseTerminal?: boolean;
13891395
terminalName?: string;
13901396
}
1397+
1398+
export interface MessageOptions {
1399+
precompiled: boolean;
1400+
}

core/llm/countTokens.ts

+9-5
Original file line numberDiff line numberDiff line change
@@ -251,13 +251,15 @@ function pruneChatHistory(
251251
chatHistory: ChatMessage[],
252252
contextLength: number,
253253
tokensForCompletion: number,
254-
): ChatMessage[] {
254+
): { prunedChatHistory: ChatMessage[]; lastMessageTruncated: boolean } {
255255
let totalTokens =
256256
tokensForCompletion +
257257
chatHistory.reduce((acc, message) => {
258258
return acc + countChatMessageTokens(modelName, message);
259259
}, 0);
260260

261+
let lastMessageTruncated = false;
262+
261263
// 0. Prune any messages that take up more than 1/3 of the context length
262264
const zippedMessagesAndTokens: [ChatMessage, number][] = [];
263265

@@ -363,8 +365,10 @@ function pruneChatHistory(
363365
tokensForCompletion,
364366
);
365367
totalTokens = contextLength;
368+
lastMessageTruncated = true;
366369
}
367-
return chatHistory;
370+
371+
return { prunedChatHistory: chatHistory, lastMessageTruncated };
368372
}
369373

370374
function messageIsEmpty(message: ChatMessage): boolean {
@@ -527,7 +531,7 @@ function compileChatMessages({
527531
functions: any[] | undefined;
528532
systemMessage: string | undefined;
529533
rules: Rule[];
530-
}): ChatMessage[] {
534+
}): { compiledChatMessages: ChatMessage[]; lastMessageTruncated: boolean } {
531535
let msgsCopy = msgs
532536
? msgs
533537
.map((msg) => ({ ...msg }))
@@ -581,7 +585,7 @@ function compileChatMessages({
581585
}
582586
}
583587

584-
const history = pruneChatHistory(
588+
const { prunedChatHistory: history, lastMessageTruncated } = pruneChatHistory(
585589
modelName,
586590
msgsCopy,
587591
contextLength,
@@ -595,7 +599,7 @@ function compileChatMessages({
595599

596600
const flattenedHistory = flattenMessages(history);
597601

598-
return flattenedHistory;
602+
return { compiledChatMessages: flattenedHistory, lastMessageTruncated };
599603
}
600604

601605
export {

core/llm/index.ts

+21-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
ILLM,
1919
LLMFullCompletionOptions,
2020
LLMOptions,
21+
MessageOptions,
2122
ModelCapability,
2223
ModelInstaller,
2324
PromptLog,
@@ -738,6 +739,15 @@ export abstract class BaseLLM implements ILLM {
738739
return { role: "assistant" as const, content: completion };
739740
}
740741

742+
compileChatMessages(
743+
options: LLMFullCompletionOptions,
744+
messages: ChatMessage[],
745+
) {
746+
let { completionOptions } = this._parseCompletionOptions(options);
747+
completionOptions = this._modifyCompletionOptions(completionOptions);
748+
return this._compileChatMessages(completionOptions, messages);
749+
}
750+
741751
protected modifyChatBody(
742752
body: ChatCompletionCreateParams,
743753
): ChatCompletionCreateParams {
@@ -762,13 +772,23 @@ export abstract class BaseLLM implements ILLM {
762772
_messages: ChatMessage[],
763773
signal: AbortSignal,
764774
options: LLMFullCompletionOptions = {},
775+
messageOptions?: MessageOptions,
765776
): AsyncGenerator<ChatMessage, PromptLog> {
766777
let { completionOptions, logEnabled } =
767778
this._parseCompletionOptions(options);
768779

769780
completionOptions = this._modifyCompletionOptions(completionOptions);
770781

771-
const messages = this._compileChatMessages(completionOptions, _messages);
782+
const { precompiled } = messageOptions ?? {};
783+
784+
let messages = _messages;
785+
if (!precompiled) {
786+
const { compiledChatMessages } = this._compileChatMessages(
787+
completionOptions,
788+
_messages,
789+
);
790+
messages = compiledChatMessages;
791+
}
772792

773793
const prompt = this.templateMessages
774794
? this.templateMessages(messages)

core/llm/streamChat.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,13 @@ export async function* llmStreamChat(
2323
void TTS.kill();
2424
}
2525

26-
const { title, legacySlashCommandData, completionOptions, messages } =
27-
msg.data;
26+
const {
27+
title,
28+
legacySlashCommandData,
29+
completionOptions,
30+
messages,
31+
messageOptions,
32+
} = msg.data;
2833

2934
const model = await configHandler.llmFromTitle(title);
3035

@@ -113,6 +118,7 @@ export async function* llmStreamChat(
113118
messages,
114119
new AbortController().signal,
115120
completionOptions,
121+
messageOptions,
116122
);
117123
let next = await gen.next();
118124
while (!next.done) {

core/protocol/core.ts

+10
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import type {
2020
FileSymbolMap,
2121
IdeSettings,
2222
LLMFullCompletionOptions,
23+
MessageOptions,
2324
ModelDescription,
2425
PromptLog,
2526
RangeInFile,
@@ -115,12 +116,21 @@ export type ToCoreFromIdeOrWebviewProtocol = {
115116
},
116117
string,
117118
];
119+
"llm/compileChat": [
120+
{
121+
title: string;
122+
messages: ChatMessage[];
123+
options: LLMFullCompletionOptions;
124+
},
125+
{ compiledChatMessages: ChatMessage[]; lastMessageTruncated: boolean },
126+
];
118127
"llm/listModels": [{ title: string }, string[] | undefined];
119128
"llm/streamChat": [
120129
{
121130
messages: ChatMessage[];
122131
completionOptions: LLMFullCompletionOptions;
123132
title: string;
133+
messageOptions: MessageOptions;
124134
legacySlashCommandData?: {
125135
command: SlashCommandDescription;
126136
input: string;

core/protocol/passThrough.ts

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export const WEBVIEW_TO_CORE_PASS_THROUGH: (keyof ToCoreFromWebviewProtocol)[] =
3535
"autocomplete/cancel",
3636
"autocomplete/accept",
3737
"tts/kill",
38+
"llm/compileChat",
3839
"llm/complete",
3940
"llm/streamChat",
4041
"llm/listModels",

extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/constants/MessageTypes.kt

+1
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ class MessageTypes {
101101
"autocomplete/cancel",
102102
"autocomplete/accept",
103103
"tts/kill",
104+
"llm/compileChat",
104105
"llm/complete",
105106
"llm/streamChat",
106107
"llm/listModels",

gui/src/pages/gui/Chat.tsx

+11
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,9 @@ export function Chat() {
132132
const hasDismissedExploreDialog = useAppSelector(
133133
(state) => state.ui.hasDismissedExploreDialog,
134134
);
135+
const warningMessage = useAppSelector(
136+
(state) => state.session.warningMessage,
137+
);
135138

136139
useEffect(() => {
137140
// Cmd + Backspace to delete current step
@@ -394,6 +397,14 @@ export function Chat() {
394397
</ErrorBoundary>
395398
</div>
396399
))}
400+
{warningMessage.length > 0 && (
401+
<div className="relative m-2 flex justify-center rounded-md border border-solid border-[#ffc107] bg-transparent p-4">
402+
<p className="thread-message text-[#ffc107]">
403+
<b>Warning: </b>
404+
{`${warningMessage}`}
405+
</p>
406+
</div>
407+
)}
397408
</StepsDiv>
398409
<div className={"relative"}>
399410
{toolCallState?.status === "generated" && <ToolCallButtons />}

gui/src/redux/slices/sessionSlice.ts

+7
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ type SessionState = {
5656
};
5757
activeToolStreamId?: [string, string];
5858
newestToolbarPreviewForInput: Record<string, string>;
59+
warningMessage: string;
5960
};
6061

6162
function isCodeToEditEqual(a: CodeToEdit, b: CodeToEdit) {
@@ -94,6 +95,7 @@ const initialState: SessionState = {
9495
},
9596
lastSessionId: undefined,
9697
newestToolbarPreviewForInput: {},
98+
warningMessage: "",
9799
};
98100

99101
export const sessionSlice = createSlice({
@@ -234,6 +236,7 @@ export const sessionSlice = createSlice({
234236
deleteMessage: (state, action: PayloadAction<number>) => {
235237
// Deletes the current assistant message and the previous user message
236238
state.history.splice(action.payload - 1, 2);
239+
state.warningMessage = "";
237240
},
238241
updateHistoryItemAtIndex: (
239242
state,
@@ -679,6 +682,9 @@ export const sessionSlice = createSlice({
679682
state.newestToolbarPreviewForInput[payload.inputId] =
680683
payload.contextItemId;
681684
},
685+
setWarningMessage: (state, action: PayloadAction<string>) => {
686+
state.warningMessage = action.payload;
687+
},
682688
},
683689
selectors: {
684690
selectIsGatheringContext: (state) => {
@@ -780,6 +786,7 @@ export const {
780786
deleteSessionMetadata,
781787
setNewestToolbarPreviewForInput,
782788
cycleMode,
789+
setWarningMessage,
783790
} = sessionSlice.actions;
784791

785792
export const {

gui/src/redux/thunks/streamNormalInput.ts

+32-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import {
99
addPromptCompletionPair,
1010
selectUseTools,
1111
setToolGenerated,
12-
streamUpdate
12+
setWarningMessage,
13+
streamUpdate,
1314
} from "../slices/sessionSlice";
1415
import { ThunkApiType } from "../store";
1516
import { callTool } from "./callTool";
@@ -39,6 +40,34 @@ export const streamNormalInput = createAsyncThunk<
3940
}
4041
const includeTools = useTools && modelSupportsTools(defaultModel);
4142

43+
const res = await extra.ideMessenger.request("llm/compileChat", {
44+
title: defaultModel.title,
45+
messages,
46+
options: includeTools
47+
? {
48+
tools: state.config.config.tools.filter(
49+
(tool) =>
50+
toolSettings[tool.function.name] !== "disabled" &&
51+
toolGroupSettings[tool.group] !== "exclude",
52+
),
53+
}
54+
: {},
55+
});
56+
57+
if (res.status === "error") {
58+
throw new Error(res.error);
59+
}
60+
61+
const { compiledChatMessages, lastMessageTruncated } = res.content;
62+
63+
if (lastMessageTruncated) {
64+
dispatch(
65+
setWarningMessage(
66+
"The provided context items are too large. They have been truncated to fit within the model's context length.",
67+
),
68+
);
69+
}
70+
4271
// Send request
4372
const gen = extra.ideMessenger.llmStreamChat(
4473
{
@@ -52,8 +81,9 @@ export const streamNormalInput = createAsyncThunk<
5281
}
5382
: {},
5483
title: defaultModel.title,
55-
messages,
84+
messages: compiledChatMessages,
5685
legacySlashCommandData,
86+
messageOptions: { precompiled: true },
5787
},
5888
streamAborter.signal,
5989
);

0 commit comments

Comments
 (0)