Skip to content

fix(mode-switch)-tools-calls-broken #5528

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion core/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1495,8 +1495,10 @@ export interface RuleWithSource {
name?: string;
slug?: string;
source:
| "default"
| "default-chat"
| "default-agent"
| "model-chat-options"
| "model-agent-options"
| "rules-block"
| "json-systemMessage"
| ".continuerules";
Expand Down
45 changes: 36 additions & 9 deletions core/llm/constructMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ import { getSystemMessageWithRules } from "./rules/getSystemMessageWithRules";
export const DEFAULT_CHAT_SYSTEM_MESSAGE_URL =
"https://github.com/continuedev/continue/blob/main/core/llm/constructMessages.ts";

export const DEFAULT_CHAT_SYSTEM_MESSAGE = `\
<important_rules>
Always include the language and file name in the info string when you write code blocks.
export const DEFAULT_AGENT_SYSTEM_MESSAGE_URL =
"https://github.com/continuedev/continue/blob/main/core/llm/constructMessages.ts";

const EDIT_MESSAGE = `\
Always include the language and file name in the info string when you write code blocks.
If you are editing "src/main.py" for example, your code block should start with '\`\`\`python src/main.py'

When addressing code modification requests, present a concise code snippet that
Expand All @@ -39,15 +41,15 @@ export const DEFAULT_CHAT_SYSTEM_MESSAGE = `\

\`\`\`language /path/to/file
// ... existing code ...

function exampleFunction() {
// ... existing code ...

{{ modified code here }}

// ... rest of function ...
}

// ... rest of code ...
\`\`\`

Expand All @@ -56,9 +58,27 @@ export const DEFAULT_CHAT_SYSTEM_MESSAGE = `\
at the beginning, middle, or end of files using these "lazy" comments. Only
provide the complete file when explicitly requested. Include a concise explanation
of changes unless the user specifically asks for code only.
`

export const DEFAULT_CHAT_SYSTEM_MESSAGE = `\
<important_rules>
You are in chat mode.

If the user asks to make changes to files offer that they can use the Apply Button on the code block, or switch to Agent Mode to make the suggested updates automatically.
If needed consisely explain to the user they can switch to agent mode using the Mode Selector dropdown and provide no other details.

${EDIT_MESSAGE}
</important_rules>`;

export const DEFAULT_AGENT_SYSTEM_MESSAGE = `\
<important_rules>
You are in agent mode.

${EDIT_MESSAGE}
</important_rules>`;

export function constructMessages(
messageMode: string,
history: ChatHistoryItem[],
baseChatOrAgentSystemMessage: string | undefined,
rules: RuleWithSource[],
Expand All @@ -71,6 +91,14 @@ export function constructMessages(
for (let i = 0; i < filteredHistory.length; i++) {
const historyItem = filteredHistory[i];

if (messageMode === "chat") {
const toolMessage: ToolResultChatMessage = historyItem.message as ToolResultChatMessage;
if (historyItem.toolCallState?.toolCallId || toolMessage.toolCallId) {
// remove all tool calls from the history
continue;
}
}

if (historyItem.message.role === "user") {
// Gather context items for user messages
let content = normalizeToMessageParts(historyItem.message);
Expand Down Expand Up @@ -100,8 +128,7 @@ export function constructMessages(
| undefined;

const systemMessage = getSystemMessageWithRules({
baseSystemMessage:
baseChatOrAgentSystemMessage ?? DEFAULT_CHAT_SYSTEM_MESSAGE,
baseSystemMessage: baseChatOrAgentSystemMessage,
rules,
userMessage: lastUserMsg,
});
Expand Down
14 changes: 14 additions & 0 deletions extensions/vscode/e2e/actions/GUI.actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,20 @@ export class GUIActions {
await dropdownOption.click();
};

public static selectModeFromDropdown = async (
view: WebView,
option: string,
) => {
const dropdownButton = await GUISelectors.getModeDropdownButton(view);
await dropdownButton.click();

const dropdownOption = await TestUtils.waitForSuccess(() => {
return GUISelectors.getModeDropdownOption(view, option);
});

await dropdownOption.click();
};

public static async sendMessage({
view,
message,
Expand Down
10 changes: 10 additions & 0 deletions extensions/vscode/e2e/selectors/GUI.selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ export class GUISelectors {
return SelectorUtils.getElementByDataTestId(view, "model-select-button");
}

public static getModeDropdownButton(view: WebView) {
return SelectorUtils.getElementByDataTestId(view, "mode-select-button");
}

public static getFirstContextProviderDropdownItem(view: WebView) {
return SelectorUtils.getElementByDataTestId(
view,
Expand Down Expand Up @@ -104,6 +108,12 @@ export class GUISelectors {
);
}

public static getModeDropdownOption(view: WebView, option: string) {
return view.findWebElement(
By.xpath(`//*[@role="listbox"]//*[contains(text(), "${option}")]`),
);
}

public static getOnboardingTabButton(view: WebView, title: string) {
return SelectorUtils.getElementByDataTestId(
view,
Expand Down
2 changes: 2 additions & 0 deletions extensions/vscode/e2e/test-continue-yaml/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ models:
- chat
- edit
- apply
capabilities:
- tool_use

- name: SYSTEM MESSAGE MOCK LLM
provider: mock
Expand Down
3 changes: 3 additions & 0 deletions extensions/vscode/e2e/test-continue/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
"provider": "mock",
"title": "TOOL MOCK LLM",
"model": "claude-3-5-sonnet-latest",
"capabilities": {
"tools": true
},
"requestOptions": {
"extraBodyProperties": {
"chatStream": [
Expand Down
15 changes: 5 additions & 10 deletions extensions/vscode/e2e/tests/GUI.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,10 +239,13 @@ describe("GUI Test", () => {
}).timeout(DEFAULT_TIMEOUT.XL);
});

describe("Chat with tools", () => {
it("should render tool call", async () => {
describe("Agent with tools", () => {
beforeEach(async () => {
await GUIActions.selectModelFromDropdown(view, "TOOL MOCK LLM");
await GUIActions.selectModeFromDropdown(view, "Agent");
});

it("should render tool call", async () => {
const [messageInput] = await GUISelectors.getMessageInputFields(view);
await messageInput.sendKeys("Hello");
await messageInput.sendKeys(Key.ENTER);
Expand All @@ -260,10 +263,6 @@ describe("GUI Test", () => {
it("should call tool after approval", async () => {
await GUIActions.toggleToolPolicy(view, "builtin_view_diff", 2);

await TestUtils.waitForSuccess(() =>
GUIActions.selectModelFromDropdown(view, "TOOL MOCK LLM"),
);

const [messageInput] = await GUISelectors.getMessageInputFields(view);
await messageInput.sendKeys("Hello");
await messageInput.sendKeys(Key.ENTER);
Expand All @@ -285,10 +284,6 @@ describe("GUI Test", () => {
it("should cancel tool", async () => {
await GUIActions.toggleToolPolicy(view, "builtin_view_diff", 2);

await TestUtils.waitForSuccess(() =>
GUIActions.selectModelFromDropdown(view, "TOOL MOCK LLM"),
);

const [messageInput] = await GUISelectors.getMessageInputFields(view);
await messageInput.sendKeys("Hello");
await messageInput.sendKeys(Key.ENTER);
Expand Down
43 changes: 31 additions & 12 deletions gui/src/components/mainInput/Lump/sections/RulesSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { RuleWithSource } from "core";
import {
DEFAULT_CHAT_SYSTEM_MESSAGE,
DEFAULT_CHAT_SYSTEM_MESSAGE_URL,
DEFAULT_AGENT_SYSTEM_MESSAGE,
DEFAULT_AGENT_SYSTEM_MESSAGE_URL
} from "core/llm/constructMessages";
import { useContext, useMemo } from "react";
import { defaultBorderRadius, vscCommandCenterActiveBorder } from "../../..";
Expand All @@ -18,7 +20,6 @@ import {
setDialogMessage,
setShowDialog,
} from "../../../../redux/slices/uiSlice";
import { getBaseSystemMessage } from "../../../../util";
import HeaderButtonWithToolTip from "../../../gui/HeaderButtonWithToolTip";
import { useFontSize } from "../../../ui/font";
import { ExploreBlocksButton } from "./ExploreBlocksButton";
Expand All @@ -30,6 +31,7 @@ interface RuleCardProps {
const RuleCard: React.FC<RuleCardProps> = ({ rule }) => {
const dispatch = useAppDispatch();
const ideMessenger = useContext(IdeMessengerContext);
const mode = useAppSelector((store) => store.session.mode);

const handleOpen = async () => {
if (rule.slug) {
Expand All @@ -41,8 +43,10 @@ const RuleCard: React.FC<RuleCardProps> = ({ rule }) => {
ideMessenger.post("openFile", {
path: rule.ruleFile,
});
} else if (rule.source === "default") {
} else if (rule.source === "default-chat" && mode === "chat") {
ideMessenger.post("openUrl", DEFAULT_CHAT_SYSTEM_MESSAGE_URL);
} else if (rule.source === "default-agent" && mode === "agent") {
ideMessenger.post("openUrl", DEFAULT_AGENT_SYSTEM_MESSAGE_URL);
} else {
ideMessenger.post("config/openProfile", {
profileId: undefined,
Expand All @@ -56,10 +60,14 @@ const RuleCard: React.FC<RuleCardProps> = ({ rule }) => {
} else {
if (rule.source === ".continuerules") {
return "Project rules";
} else if (rule.source === "default") {
} else if (rule.source === "default-chat") {
return "Default chat system message";
} else if (rule.source === "default-agent") {
return "Default agent system message";
} else if (rule.source === "json-systemMessage") {
return "JSON systemMessage)";
} else if (rule.source === "model-agent-options") {
return "Base System Agent Message";
} else if (rule.source === "model-chat-options") {
return "Base System Chat Message";
} else {
Expand Down Expand Up @@ -104,7 +112,7 @@ const RuleCard: React.FC<RuleCardProps> = ({ rule }) => {
<HeaderButtonWithToolTip onClick={onClickExpand} text="Expand">
<ArrowsPointingOutIcon className="h-3 w-3 text-gray-400" />
</HeaderButtonWithToolTip>{" "}
{rule.source === "default" ? (
{rule.source === "default-chat" || rule.source === "default-agent" ? (
<HeaderButtonWithToolTip onClick={handleOpen} text="View">
<EyeIcon className="h-3 w-3 text-gray-400" />
</HeaderButtonWithToolTip>
Expand Down Expand Up @@ -181,19 +189,30 @@ export function RulesSection() {
);
}
}
// Add a displayed rule for the base system chat message when in chat mode
if (mode === "chat" || mode === "agent") {
const baseMessage = getBaseSystemMessage(config.selectedModelByRole.chat, mode)
if (baseMessage) {

if (mode === "agent") {
if (config.selectedModelByRole.chat?.baseAgentSystemMessage) {
rules.unshift({
rule: config.selectedModelByRole.chat?.baseAgentSystemMessage,
source: "model-agent-options",
})
} else {
rules.unshift({
rule: DEFAULT_AGENT_SYSTEM_MESSAGE,
source: "default-agent",
})
}
} else {
if (config.selectedModelByRole.chat?.baseChatSystemMessage) {
rules.unshift({
rule: baseMessage,
rule: config.selectedModelByRole.chat?.baseChatSystemMessage,
source: "model-chat-options",
});
})
} else {
rules.unshift({
rule: DEFAULT_CHAT_SYSTEM_MESSAGE,
source: "default",
});
source: "default-chat",
})
}
}

Expand Down
17 changes: 11 additions & 6 deletions gui/src/redux/selectors/selectActiveTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,18 @@ export const selectActiveTools = createSelector(
(store: RootState) => store.ui.toolGroupSettings,
],
(mode, tools, policies, groupPolicies): Tool[] => {
if (mode !== "agent") {
if (mode === "chat") {
return [];
}
else if (mode === "agent") {
return tools.filter(
(tool) =>
policies[tool.function.name] !== "disabled" &&
groupPolicies[tool.group] !== "exclude",
);
}
else {
return [];
}
return tools.filter(
(tool) =>
policies[tool.function.name] !== "disabled" &&
groupPolicies[tool.group] !== "exclude",
);
},
);
3 changes: 2 additions & 1 deletion gui/src/redux/thunks/streamResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,9 @@ export const streamResponseThunk = createAsyncThunk<
const messageMode = getState().session.mode

const baseChatOrAgentSystemMessage = getBaseSystemMessage(selectedChatModel, messageMode)

const messages = constructMessages(
messageMode,
[...updatedHistory],
baseChatOrAgentSystemMessage,
state.config.config.rules,
Expand Down
1 change: 1 addition & 0 deletions gui/src/redux/thunks/streamResponseAfterToolCall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export const streamResponseAfterToolCall = createAsyncThunk<
const baseChatOrAgentSystemMessage = getBaseSystemMessage(selectedChatModel, messageMode)

const messages = constructMessages(
messageMode,
[...updatedHistory],
baseChatOrAgentSystemMessage,
state.config.config.rules,
Expand Down
5 changes: 3 additions & 2 deletions gui/src/util/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ProfileDescription } from "core/config/ProfileLifecycleManager";
import _ from "lodash";
import { KeyboardEvent } from "react";
import { getLocalStorage } from "./localStorage";
import { DEFAULT_CHAT_SYSTEM_MESSAGE, DEFAULT_AGENT_SYSTEM_MESSAGE } from "core/llm/constructMessages";

export type Platform = "mac" | "linux" | "windows" | "unknown";

Expand Down Expand Up @@ -122,9 +123,9 @@ export function isLocalProfile(profile: ProfileDescription): boolean {
export function getBaseSystemMessage(modelDetails: ModelDescription | null, mode: MessageModes) {
let baseChatOrAgentSystemMessage: string|undefined
if(mode === 'agent') {
baseChatOrAgentSystemMessage = modelDetails?.baseAgentSystemMessage ?? modelDetails?.baseChatSystemMessage // fallback to chat system message if agent system message is unavailable
baseChatOrAgentSystemMessage = modelDetails?.baseAgentSystemMessage ?? DEFAULT_AGENT_SYSTEM_MESSAGE;
} else {
baseChatOrAgentSystemMessage = modelDetails?.baseChatSystemMessage
baseChatOrAgentSystemMessage = modelDetails?.baseChatSystemMessage ?? DEFAULT_CHAT_SYSTEM_MESSAGE;
}
return baseChatOrAgentSystemMessage
}
Loading