mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-21 17:27:54 +00:00
feat: Enhance replace tool reliability with multi-stage edit correction
This commit significantly improves the `replace` tool's robustness by introducing a multi-stage correction mechanism. This directly addresses challenges with LLM-generated tool inputs, particularly the over-escaping of strings sometimes observed with Gemini models, and other minor discrepancies that previously led to failed edits.
The correction process is as follows:
1. **Targeted Unescaping:** The system first applies a specialized unescaping function to the `old_string` and `new_string` to counteract common LLM-induced escaping patterns.
2. **LLM-Powered Discrepancy Resolution:** If a unique match for the `old_string` is still not found, the system leverages a Gemini model (`gemini-2.5-flash-preview-04-17`) to:
* Identify the most probable intended `old_string` in the file by intelligently correcting minor formatting or escaping differences.
* Adjust the `new_string` to correspond with any corrections made to the `old_string`, maintaining the original edit's intent.
This enhancement makes the `replace` tool more resilient and effective, leading to a higher success rate for automated code modifications. The `expected_replacements` parameter has been removed as the tool now focuses on finding a single, unique, and correctable match. The tool's description and error reporting have been updated to reflect these new capabilities.
Fixes https://b.corp.google.com/issues/416933027
This commit is contained in:
committed by
N. Taylor Mullen
parent
5ec254253f
commit
3217576743
117
packages/server/src/utils/editCorrector.test.ts
Normal file
117
packages/server/src/utils/editCorrector.test.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import {
|
||||
countOccurrences,
|
||||
unescapeStringForGeminiBug,
|
||||
} from './editCorrector.js';
|
||||
|
||||
describe('editCorrector', () => {
|
||||
describe('countOccurrences', () => {
|
||||
it('should return 0 for empty string', () => {
|
||||
expect(countOccurrences('', 'a')).toBe(0);
|
||||
});
|
||||
|
||||
it('should return 0 for empty substring', () => {
|
||||
expect(countOccurrences('abc', '')).toBe(0);
|
||||
});
|
||||
|
||||
it('should return 0 if substring is not found', () => {
|
||||
expect(countOccurrences('abc', 'd')).toBe(0);
|
||||
});
|
||||
|
||||
it('should return 1 if substring is found once', () => {
|
||||
expect(countOccurrences('abc', 'b')).toBe(1);
|
||||
});
|
||||
|
||||
it('should return correct count for multiple occurrences', () => {
|
||||
expect(countOccurrences('ababa', 'a')).toBe(3);
|
||||
expect(countOccurrences('ababab', 'ab')).toBe(3);
|
||||
});
|
||||
|
||||
it('should count non-overlapping occurrences', () => {
|
||||
expect(countOccurrences('aaaaa', 'aa')).toBe(2); // Non-overlapping: aa_aa_
|
||||
expect(countOccurrences('ababab', 'aba')).toBe(1); // Non-overlapping: aba_ab -> 1
|
||||
});
|
||||
|
||||
it('should correctly count occurrences when substring is longer', () => {
|
||||
expect(countOccurrences('abc', 'abcdef')).toBe(0);
|
||||
});
|
||||
|
||||
it('should be case sensitive', () => {
|
||||
expect(countOccurrences('abcABC', 'a')).toBe(1);
|
||||
expect(countOccurrences('abcABC', 'A')).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('unescapeStringForGeminiBug', () => {
|
||||
it('should unescape common sequences', () => {
|
||||
expect(unescapeStringForGeminiBug('\\n')).toBe('\n');
|
||||
expect(unescapeStringForGeminiBug('\\t')).toBe('\t');
|
||||
expect(unescapeStringForGeminiBug("\\'")).toBe("'");
|
||||
expect(unescapeStringForGeminiBug('\\"')).toBe('"');
|
||||
expect(unescapeStringForGeminiBug('\\`')).toBe('`');
|
||||
});
|
||||
|
||||
it('should handle multiple escaped sequences', () => {
|
||||
expect(unescapeStringForGeminiBug('Hello\\nWorld\\tTest')).toBe(
|
||||
'Hello\nWorld\tTest',
|
||||
);
|
||||
});
|
||||
|
||||
it('should not alter already correct sequences', () => {
|
||||
expect(unescapeStringForGeminiBug('\n')).toBe('\n');
|
||||
expect(unescapeStringForGeminiBug('Correct string')).toBe(
|
||||
'Correct string',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle mixed correct and incorrect sequences', () => {
|
||||
expect(unescapeStringForGeminiBug('\\nCorrect\t\\`')).toBe(
|
||||
'\nCorrect\t`',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle backslash followed by actual newline character', () => {
|
||||
expect(unescapeStringForGeminiBug('\\\n')).toBe('\n');
|
||||
expect(unescapeStringForGeminiBug('First line\\\nSecond line')).toBe(
|
||||
'First line\nSecond line',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle multiple backslashes before an escapable character', () => {
|
||||
expect(unescapeStringForGeminiBug('\\\\n')).toBe('\n'); // \\n -> \n
|
||||
expect(unescapeStringForGeminiBug('\\\\\\t')).toBe('\t'); // \\\t -> \t
|
||||
expect(unescapeStringForGeminiBug('\\\\\\\\`')).toBe('`'); // \\\\` -> `
|
||||
});
|
||||
|
||||
it('should return empty string for empty input', () => {
|
||||
expect(unescapeStringForGeminiBug('')).toBe('');
|
||||
});
|
||||
|
||||
it('should not alter strings with no targeted escape sequences', () => {
|
||||
expect(unescapeStringForGeminiBug('abc def')).toBe('abc def');
|
||||
// \\F and \\S are not targeted escapes, so they should remain as \\F and \\S
|
||||
expect(unescapeStringForGeminiBug('C:\\Folder\\File')).toBe(
|
||||
'C:\\Folder\\File',
|
||||
);
|
||||
});
|
||||
|
||||
it('should correctly process strings with some targeted escapes', () => {
|
||||
// \\U is not targeted, \\n is.
|
||||
expect(unescapeStringForGeminiBug('C:\\Users\\name')).toBe(
|
||||
'C:\\Users\name',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle complex cases with mixed slashes and characters', () => {
|
||||
expect(
|
||||
unescapeStringForGeminiBug('\\\\\\nLine1\\\nLine2\\tTab\\\\`Tick\\"'),
|
||||
).toBe('\nLine1\nLine2\tTab`Tick"');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user