Skip to content

Commit 704e405

Browse files
authored
tools: improve string_replace and add healing (ft. Gemini) (#251)
* test implementation of gemini-inspired model based healing * rationalize types in string replace tools * cleanup and add telemetry for string_replace healing * prompting improvements * add failsafe for string-replace healing * update snapshot
1 parent ae36466 commit 704e405

File tree

10 files changed

+837
-172
lines changed

10 files changed

+837
-172
lines changed

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -935,7 +935,7 @@
935935
"name": "copilot_replaceString",
936936
"toolReferenceName": "replaceString",
937937
"displayName": "%copilot.tools.replaceString.name%",
938-
"modelDescription": "This is a tool for making edits in an existing file in the workspace. For moving or renaming files, use run in terminal tool with the 'mv' command instead. For larger edits, split them into smaller edits and call the edit tool multiple times to ensure accuracy. Before editing, always ensure you have the context to understand the file's contents and context. To edit a file, provide: 1) filePath (absolute path), 2) oldString (must exactly match, including whitespace and indentation, uniquely identifying a single occurrence), and 3) newString (replacement text). Each use of this tool replaces exactly ONE occurrence of oldString. CRITICAL REQUIREMENTS: ensure oldString uniquely identifies the change by including at least 3-5 lines of context both before and after the target text, preserving whitespace and indentation exactly. Never use ...existing code... comments in the oldString or newString. Edits must result in valid, idiomatic code and not leave the file broken!",
938+
"modelDescription": "This is a tool for making edits in an existing file in the workspace. For moving or renaming files, use run in terminal tool with the 'mv' command instead. For larger edits, split them into smaller edits and call the edit tool multiple times to ensure accuracy. Before editing, always ensure you have the context to understand the file's contents and context. To edit a file, provide: 1) filePath (absolute path), 2) oldString (MUST be the exact literal text to replace including all whitespace, indentation, newlines, and surrounding code etc), and 3) newString (MUST be the exact literal text to replace \\`oldString\\` with (also including all whitespace, indentation, newlines, and surrounding code etc.). Ensure the resulting code is correct and idiomatic.). Each use of this tool replaces exactly ONE occurrence of oldString.\n\nCRITICAL for \\`oldString\\`: Must uniquely identify the single instance to change. Include at least 3 lines of context BEFORE and AFTER the target text, matching whitespace and indentation precisely. If this string matches multiple locations, or does not match exactly, the tool will fail. Never use ...existing code... comments in the oldString or newString.",
939939
"when": "!config.github.copilot.chat.disableReplaceTool",
940940
"inputSchema": {
941941
"type": "object",
@@ -946,11 +946,11 @@
946946
},
947947
"oldString": {
948948
"type": "string",
949-
"description": "The string to be replaced in the file. Never use ...existing code... comments in the oldString."
949+
"description": "The exact literal text to replace, preferably unescaped. For single replacements (default), include at least 3 lines of context BEFORE and AFTER the target text, matching whitespace and indentation precisely. For multiple replacements, specify expected_replacements parameter. If this string is not the exact literal text (i.e. you escaped it) or does not match exactly, the tool will fail."
950950
},
951951
"newString": {
952952
"type": "string",
953-
"description": "The replacement string. Can be empty to delete oldString."
953+
"description": "The exact literal text to replace `old_string` with, preferably unescaped. Provide the EXACT text. Ensure the resulting code is correct and idiomatic."
954954
}
955955
},
956956
"required": [

src/extension/prompts/node/agent/agentInstructions.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,18 +75,18 @@ export class DefaultAgentPrompt extends PromptElement<DefaultAgentPromptProps> {
7575
{hasReplaceStringTool ?
7676
<>
7777
Before you edit an existing file, make sure you either already have it in the provided context, or read it with the {ToolName.ReadFile} tool, so that you can make proper changes.<br />
78-
Use the {ToolName.ReplaceString} tool to replace a string in a file, but only if you are sure that the string is unique enough to not cause any issues. You can use this tool multiple times per file.<br />
79-
Use the {ToolName.EditFile} tool to insert code into a file.<br />
78+
Use the {ToolName.ReplaceString} tool to edit files, paying attention to context to ensure your replacement is unique. You can use this tool multiple times per file.<br />
79+
Use the {ToolName.EditFile} tool to insert code into a file ONLY if {ToolName.ReplaceString} has failed.<br />
8080
When editing files, group your changes by file.<br />
8181
NEVER show the changes to the user, just call the tool, and the edits will be applied and shown to the user.<br />
82-
NEVER print a codeblock that represents a change to a file, use {ToolName.EditFile} or {ToolName.ReplaceString} instead.<br />
82+
NEVER print a codeblock that represents a change to a file, use {ToolName.ReplaceString} or {ToolName.EditFile} instead.<br />
8383
For each file, give a short description of what needs to be changed, then use the {ToolName.ReplaceString} or {ToolName.EditFile} tools. You can use any tool multiple times in a response, and you can keep writing text after using a tool.<br /></> :
8484
<>
8585
Don't try to edit an existing file without reading it first, so you can make changes properly.<br />
86-
Use the {ToolName.EditFile} tool to edit files. When editing files, group your changes by file.<br />
86+
Use the {ToolName.ReplaceString} tool to edit files. When editing files, group your changes by file.<br />
8787
NEVER show the changes to the user, just call the tool, and the edits will be applied and shown to the user.<br />
88-
NEVER print a codeblock that represents a change to a file, use {ToolName.EditFile} instead.<br />
89-
For each file, give a short description of what needs to be changed, then use the {ToolName.EditFile} tool. You can use any tool multiple times in a response, and you can keep writing text after using a tool.<br />
88+
NEVER print a codeblock that represents a change to a file, use {ToolName.ReplaceString} instead.<br />
89+
For each file, give a short description of what needs to be changed, then use the {ToolName.ReplaceString} tool. You can use any tool multiple times in a response, and you can keep writing text after using a tool.<br />
9090
</>}
9191
<GenericEditingTips {...this.props} />
9292
The {ToolName.EditFile} tool is very smart and can understand how to apply your edits to the user's files, you just need to provide minimal hints.<br />

src/extension/prompts/node/agent/agentPrompt.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { isDefined } from '@vscode/test-electron/out/util';
88
import type { ChatRequestEditedFileEvent, LanguageModelToolInformation, NotebookEditor, TaskDefinition, TextEditor } from 'vscode';
99
import { ChatLocation } from '../../../../platform/chat/common/commonTypes';
1010
import { ConfigKey, IConfigurationService } from '../../../../platform/configuration/common/configurationService';
11+
import { modelNeedsStrongReplaceStringHint } from '../../../../platform/endpoint/common/chatModelCapabilities';
1112
import { CacheType } from '../../../../platform/endpoint/common/endpointTypes';
1213
import { IEnvService, OperatingSystem } from '../../../../platform/env/common/envService';
1314
import { getGitHubRepoInfoFromContext, IGitService } from '../../../../platform/git/common/gitService';
@@ -285,7 +286,7 @@ export class AgentUserMessage extends PromptElement<AgentUserMessageProps> {
285286
<Tag name='reminderInstructions'>
286287
{/* Critical reminders that are effective when repeated right next to the user message */}
287288
{getKeepGoingReminder(this.props.endpoint.family)}
288-
{getEditingReminder(hasEditFileTool, hasReplaceStringTool)}
289+
{getEditingReminder(hasEditFileTool, hasReplaceStringTool, modelNeedsStrongReplaceStringHint(this.props.endpoint))}
289290
<NotebookReminderInstructions chatVariables={this.props.chatVariables} query={this.props.request} />
290291
</Tag>
291292
{query && <Tag name='userRequest' priority={900} flexGrow={7}>{query + attachmentHint}</Tag>}
@@ -596,15 +597,21 @@ class AgentTasksInstructions extends PromptElement {
596597
}
597598
}
598599

599-
export function getEditingReminder(hasEditFileTool: boolean, hasReplaceStringTool: boolean) {
600+
export function getEditingReminder(hasEditFileTool: boolean, hasReplaceStringTool: boolean, useStrongReplaceStringHint: boolean) {
600601
const lines = [];
601602
if (hasEditFileTool) {
602603
lines.push(<>When using the {ToolName.EditFile} tool, avoid repeating existing code, instead use a line comment with \`{EXISTING_CODE_MARKER}\` to represent regions of unchanged code.<br /></>);
603-
604604
}
605605
if (hasReplaceStringTool) {
606606
lines.push(<>When using the {ToolName.ReplaceString} tool, include 3-5 lines of unchanged code before and after the string you want to replace, to make it unambiguous which part of the file should be edited.<br /></>);
607607
}
608+
if (hasEditFileTool && hasReplaceStringTool) {
609+
if (useStrongReplaceStringHint) {
610+
lines.push(<>You must always try making file edits using {ToolName.ReplaceString} tool. NEVER use {ToolName.EditFile} unless told to by the user or by a tool.</>);
611+
} else {
612+
lines.push(<>It is much faster to edit using the {ToolName.ReplaceString} tool. Prefer {ToolName.ReplaceString} for making edits and only fall back to {ToolName.EditFile} if it fails.</>);
613+
}
614+
}
608615

609616
return lines;
610617
}

src/extension/prompts/node/agent/test/__snapshots__/agentPrompt.spec.tsx.snap

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -175,11 +175,11 @@ Tools can be disabled by the user. You may see tools used previously in the conv
175175
</toolUseInstructions>
176176
<editFileInstructions>
177177
Before you edit an existing file, make sure you either already have it in the provided context, or read it with the read_file tool, so that you can make proper changes.
178-
Use the replace_string_in_file tool to replace a string in a file, but only if you are sure that the string is unique enough to not cause any issues. You can use this tool multiple times per file.
179-
Use the insert_edit_into_file tool to insert code into a file.
178+
Use the replace_string_in_file tool to edit files, paying attention to context to ensure your replacement is unique. You can use this tool multiple times per file.
179+
Use the insert_edit_into_file tool to insert code into a file ONLY if replace_string_in_file has failed.
180180
When editing files, group your changes by file.
181181
NEVER show the changes to the user, just call the tool, and the edits will be applied and shown to the user.
182-
NEVER print a codeblock that represents a change to a file, use insert_edit_into_file or replace_string_in_file instead.
182+
NEVER print a codeblock that represents a change to a file, use replace_string_in_file or insert_edit_into_file instead.
183183
For each file, give a short description of what needs to be changed, then use the replace_string_in_file or insert_edit_into_file tools. You can use any tool multiple times in a response, and you can keep writing text after using a tool.
184184
Follow best practices when editing files. If a popular external library exists to solve a problem, use it and properly install the package e.g. creating a "requirements.txt".
185185
If you're building a webapp from scratch, give it a beautiful and modern UI.
@@ -259,7 +259,7 @@ copilot_cache_control: {"type":"ephemeral"}
259259
<reminderInstructions>
260260
When using the insert_edit_into_file tool, avoid repeating existing code, instead use a line comment with /\`...existing code.../\` to represent regions of unchanged code.
261261
When using the replace_string_in_file tool, include 3-5 lines of unchanged code before and after the string you want to replace, to make it unambiguous which part of the file should be edited.
262-
262+
It is much faster to edit using the replace_string_in_file tool. Prefer replace_string_in_file for making edits and only fall back to insert_edit_into_file if it fails.
263263
</reminderInstructions>
264264
<userRequest>
265265
hello

src/extension/prompts/node/panel/editCodePrompt2.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import { PromptElement, PromptSizing, SystemMessage, UserMessage } from '@vscode/prompt-tsx';
77
import { ConfigKey, IConfigurationService } from '../../../../platform/configuration/common/configurationService';
8-
import { modelPrefersInstructionsAfterHistory } from '../../../../platform/endpoint/common/chatModelCapabilities';
8+
import { modelNeedsStrongReplaceStringHint, modelPrefersInstructionsAfterHistory } from '../../../../platform/endpoint/common/chatModelCapabilities';
99
import { IExperimentationService } from '../../../../platform/telemetry/common/nullExperimentationService';
1010
import { isLocation, isUri } from '../../../../util/common/types';
1111
import { ToolName } from '../../../tools/common/toolNames';
@@ -148,7 +148,7 @@ export class EditCode2UserMessage extends PromptElement<AgentPromptProps> {
148148
<ChatToolReferences flexGrow={4} priority={898} promptContext={this.props.promptContext} documentContext={this.props.documentContext} />
149149
<ChatVariables flexGrow={3} priority={898} chatVariables={chatVariables} />
150150
<Tag name='reminder'>
151-
{getEditingReminder(hasEditFileTool, hasReplaceStringTool)}
151+
{getEditingReminder(hasEditFileTool, hasReplaceStringTool, modelNeedsStrongReplaceStringHint(this.props.endpoint))}
152152
<NotebookReminderInstructions chatVariables={chatVariables} query={query} />
153153
<NewFilesLocationHint />
154154
</Tag>

0 commit comments

Comments
 (0)