edit_file: Add diff-fenced output format (#32737)

This format is enabled for Google models as they seem to prefer it.
A relevant unit eval's pass rate has increased from 0.77 to 0.98.

Diff-fenced format looks like this (markdown fences and a line hint are
optional):

```diff
<<<<<<< SEARCH line=42
...
=======
...
>>>>>>> REPLACE
```

Release Notes:

- Agent: Gemini models now use the diff-fenced format when making edits
This commit is contained in:
Oleksiy Syvokon 2025-06-16 17:28:18 +03:00 committed by GitHub
parent 8df6ce2aac
commit fceba6c795
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 667 additions and 79 deletions

View file

@ -8,6 +8,7 @@ use crate::{Template, Templates};
use anyhow::Result;
use assistant_tool::ActionLog;
use create_file_parser::{CreateFileParser, CreateFileParserEvent};
pub use edit_parser::EditFormat;
use edit_parser::{EditParser, EditParserEvent, EditParserMetrics};
use futures::{
Stream, StreamExt,
@ -41,13 +42,23 @@ impl Template for CreateFilePromptTemplate {
}
#[derive(Serialize)]
struct EditFilePromptTemplate {
struct EditFileXmlPromptTemplate {
path: Option<PathBuf>,
edit_description: String,
}
impl Template for EditFilePromptTemplate {
const TEMPLATE_NAME: &'static str = "edit_file_prompt.hbs";
impl Template for EditFileXmlPromptTemplate {
const TEMPLATE_NAME: &'static str = "edit_file_prompt_xml.hbs";
}
#[derive(Serialize)]
struct EditFileDiffFencedPromptTemplate {
path: Option<PathBuf>,
edit_description: String,
}
impl Template for EditFileDiffFencedPromptTemplate {
const TEMPLATE_NAME: &'static str = "edit_file_prompt_diff_fenced.hbs";
}
#[derive(Clone, Debug, PartialEq, Eq)]
@ -70,6 +81,7 @@ pub struct EditAgent {
action_log: Entity<ActionLog>,
project: Entity<Project>,
templates: Arc<Templates>,
edit_format: EditFormat,
}
impl EditAgent {
@ -78,12 +90,14 @@ impl EditAgent {
project: Entity<Project>,
action_log: Entity<ActionLog>,
templates: Arc<Templates>,
edit_format: EditFormat,
) -> Self {
EditAgent {
model,
project,
action_log,
templates,
edit_format,
}
}
@ -209,14 +223,23 @@ impl EditAgent {
let this = self.clone();
let (events_tx, events_rx) = mpsc::unbounded();
let conversation = conversation.clone();
let edit_format = self.edit_format;
let output = cx.spawn(async move |cx| {
let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot())?;
let path = cx.update(|cx| snapshot.resolve_file_path(cx, true))?;
let prompt = EditFilePromptTemplate {
path,
edit_description,
}
.render(&this.templates)?;
let prompt = match edit_format {
EditFormat::XmlTags => EditFileXmlPromptTemplate {
path,
edit_description,
}
.render(&this.templates)?,
EditFormat::DiffFenced => EditFileDiffFencedPromptTemplate {
path,
edit_description,
}
.render(&this.templates)?,
};
let edit_chunks = this
.request(conversation, CompletionIntent::EditFile, prompt, cx)
.await?;
@ -236,7 +259,7 @@ impl EditAgent {
self.action_log
.update(cx, |log, cx| log.buffer_read(buffer.clone(), cx))?;
let (output, edit_events) = Self::parse_edit_chunks(edit_chunks, cx);
let (output, edit_events) = Self::parse_edit_chunks(edit_chunks, self.edit_format, cx);
let mut edit_events = edit_events.peekable();
while let Some(edit_event) = Pin::new(&mut edit_events).peek().await {
// Skip events until we're at the start of a new edit.
@ -350,6 +373,7 @@ impl EditAgent {
fn parse_edit_chunks(
chunks: impl 'static + Send + Stream<Item = Result<String, LanguageModelCompletionError>>,
edit_format: EditFormat,
cx: &mut AsyncApp,
) -> (
Task<Result<EditAgentOutput>>,
@ -359,7 +383,7 @@ impl EditAgent {
let output = cx.background_spawn(async move {
pin_mut!(chunks);
let mut parser = EditParser::new();
let mut parser = EditParser::new(edit_format);
let mut raw_edits = String::new();
while let Some(chunk) = chunks.next().await {
match chunk {
@ -1355,7 +1379,13 @@ mod tests {
let project = Project::test(FakeFs::new(cx.executor()), [], cx).await;
let model = Arc::new(FakeLanguageModel::default());
let action_log = cx.new(|_| ActionLog::new(project.clone()));
EditAgent::new(model, project, action_log, Templates::new())
EditAgent::new(
model,
project,
action_log,
Templates::new(),
EditFormat::XmlTags,
)
}
#[gpui::test(iterations = 10)]