ep: Skip context retrieval when already performed (#51100)

Oleksiy Syvokon created

Previously we didn't distinguish between an empty `.related_files[]` and
a case where context collection hadn't run yet. As a result, context
retrieval was always attempted for examples with empty `related_files`.

Release Notes:

- N/A

Change summary

crates/edit_prediction/src/fim.rs                      |  2 
crates/edit_prediction/src/mercury.rs                  |  4 +-
crates/edit_prediction/src/prediction.rs               |  2 
crates/edit_prediction/src/sweep_ai.rs                 |  2 
crates/edit_prediction/src/zeta.rs                     |  2 
crates/edit_prediction_cli/src/format_prompt.rs        |  5 ++
crates/edit_prediction_cli/src/load_project.rs         |  3 -
crates/edit_prediction_cli/src/retrieve_context.rs     | 20 ++++-------
crates/edit_prediction_cli/src/reversal_tracking.rs    |  2 
crates/edit_prediction_ui/src/rate_prediction_modal.rs |  8 ++++
crates/zeta_prompt/src/zeta_prompt.rs                  | 19 ++++++----
11 files changed, 37 insertions(+), 32 deletions(-)

Detailed changes

crates/edit_prediction/src/fim.rs 🔗

@@ -73,7 +73,7 @@ pub fn request_prediction(
 
         let inputs = ZetaPromptInput {
             events,
-            related_files: Vec::new(),
+            related_files: Some(Vec::new()),
             cursor_offset_in_excerpt: cursor_offset - excerpt_offset_range.start,
             cursor_path: full_path.clone(),
             excerpt_start_row: Some(excerpt_range.start.row),

crates/edit_prediction/src/mercury.rs 🔗

@@ -91,7 +91,7 @@ impl Mercury {
 
             let inputs = zeta_prompt::ZetaPromptInput {
                 events,
-                related_files,
+                related_files: Some(related_files),
                 cursor_offset_in_excerpt: cursor_point.to_offset(&snapshot)
                     - context_offset_range.start,
                 cursor_path: full_path.clone(),
@@ -260,7 +260,7 @@ fn build_prompt(inputs: &ZetaPromptInput) -> String {
         &mut prompt,
         RECENTLY_VIEWED_SNIPPETS_START..RECENTLY_VIEWED_SNIPPETS_END,
         |prompt| {
-            for related_file in inputs.related_files.iter() {
+            for related_file in inputs.related_files.as_deref().unwrap_or_default().iter() {
                 for related_excerpt in &related_file.excerpts {
                     push_delimited(
                         prompt,

crates/edit_prediction/src/prediction.rs 🔗

@@ -156,7 +156,7 @@ mod tests {
             model_version: None,
             inputs: ZetaPromptInput {
                 events: vec![],
-                related_files: vec![],
+                related_files: Some(vec![]),
                 cursor_path: Path::new("path.txt").into(),
                 cursor_offset_in_excerpt: 0,
                 cursor_excerpt: "".into(),

crates/edit_prediction/src/sweep_ai.rs 🔗

@@ -212,7 +212,7 @@ impl SweepAi {
 
             let ep_inputs = zeta_prompt::ZetaPromptInput {
                 events: inputs.events,
-                related_files: inputs.related_files.clone(),
+                related_files: Some(inputs.related_files.clone()),
                 cursor_path: full_path.clone(),
                 cursor_excerpt: request_body.file_contents.clone().into(),
                 cursor_offset_in_excerpt: request_body.cursor_position,

crates/edit_prediction/src/zeta.rs 🔗

@@ -509,7 +509,7 @@ pub fn zeta2_prompt_input(
         cursor_offset_in_excerpt,
         excerpt_start_row: Some(full_context_start_row),
         events,
-        related_files,
+        related_files: Some(related_files),
         excerpt_ranges,
         experiment: preferred_experiment,
         in_open_source_repo: is_open_source,

crates/edit_prediction_cli/src/format_prompt.rs 🔗

@@ -259,7 +259,10 @@ impl TeacherPrompt {
     }
 
     pub fn format_context(example: &Example) -> String {
-        let related_files = example.prompt_inputs.as_ref().map(|pi| &pi.related_files);
+        let related_files = example
+            .prompt_inputs
+            .as_ref()
+            .and_then(|pi| pi.related_files.as_deref());
         let Some(related_files) = related_files else {
             return "(No context)".to_string();
         };

crates/edit_prediction_cli/src/load_project.rs 🔗

@@ -71,8 +71,7 @@ pub async fn run_load_project(
     let existing_related_files = example
         .prompt_inputs
         .take()
-        .map(|inputs| inputs.related_files)
-        .unwrap_or_default();
+        .and_then(|inputs| inputs.related_files);
 
     let (prompt_inputs, language_name) = buffer.read_with(&cx, |buffer, _cx| {
         let snapshot = buffer.snapshot();

crates/edit_prediction_cli/src/retrieve_context.rs 🔗

@@ -20,18 +20,12 @@ pub async fn run_context_retrieval(
     example_progress: &ExampleProgress,
     mut cx: AsyncApp,
 ) -> anyhow::Result<()> {
-    if example.prompt_inputs.is_some() {
-        if example.spec.repository_url.is_empty() {
-            return Ok(());
-        }
-
-        if example
-            .prompt_inputs
-            .as_ref()
-            .is_some_and(|inputs| !inputs.related_files.is_empty())
-        {
-            return Ok(());
-        }
+    if example
+        .prompt_inputs
+        .as_ref()
+        .is_some_and(|inputs| inputs.related_files.is_some())
+    {
+        return Ok(());
     }
 
     run_load_project(example, app_state.clone(), example_progress, cx.clone()).await?;
@@ -72,7 +66,7 @@ pub async fn run_context_retrieval(
     step_progress.set_info(format!("{} excerpts", excerpt_count), InfoStyle::Normal);
 
     if let Some(prompt_inputs) = example.prompt_inputs.as_mut() {
-        prompt_inputs.related_files = context_files;
+        prompt_inputs.related_files = Some(context_files);
     }
     Ok(())
 }

crates/edit_prediction_cli/src/reversal_tracking.rs 🔗

@@ -668,7 +668,7 @@ mod tests {
             cursor_offset_in_excerpt: 0,
             excerpt_start_row,
             events,
-            related_files: Vec::new(),
+            related_files: Some(Vec::new()),
             excerpt_ranges: ExcerptRanges {
                 editable_150: 0..content.len(),
                 editable_180: 0..content.len(),

crates/edit_prediction_ui/src/rate_prediction_modal.rs 🔗

@@ -402,7 +402,13 @@ impl RatePredictionsModal {
 
             write!(&mut formatted_inputs, "## Related files\n\n").unwrap();
 
-            for included_file in prediction.inputs.related_files.iter() {
+            for included_file in prediction
+                .inputs
+                .related_files
+                .as_deref()
+                .unwrap_or_default()
+                .iter()
+            {
                 write!(
                     &mut formatted_inputs,
                     "### {}\n\n",

crates/zeta_prompt/src/zeta_prompt.rs 🔗

@@ -51,7 +51,8 @@ pub struct ZetaPromptInput {
     #[serde(default, skip_serializing_if = "Option::is_none")]
     pub excerpt_start_row: Option<u32>,
     pub events: Vec<Arc<Event>>,
-    pub related_files: Vec<RelatedFile>,
+    #[serde(default)]
+    pub related_files: Option<Vec<RelatedFile>>,
     /// These ranges let the server select model-appropriate subsets.
     pub excerpt_ranges: ExcerptRanges,
     /// The name of the edit prediction model experiment to use.
@@ -350,17 +351,19 @@ pub fn format_prompt_with_budget_for_format(
         resolve_cursor_region(input, format);
     let path = &*input.cursor_path;
 
+    let empty_files = Vec::new();
+    let input_related_files = input.related_files.as_deref().unwrap_or(&empty_files);
     let related_files = if let Some(cursor_excerpt_start_row) = input.excerpt_start_row {
         let relative_row_range = offset_range_to_row_range(&input.cursor_excerpt, context_range);
         let row_range = relative_row_range.start + cursor_excerpt_start_row
             ..relative_row_range.end + cursor_excerpt_start_row;
         &filter_redundant_excerpts(
-            input.related_files.clone(),
+            input_related_files.to_vec(),
             input.cursor_path.as_ref(),
             row_range,
         )
     } else {
-        &input.related_files
+        input_related_files
     };
 
     match format {
@@ -3863,7 +3866,7 @@ mod tests {
             cursor_offset_in_excerpt: cursor_offset,
             excerpt_start_row: None,
             events: events.into_iter().map(Arc::new).collect(),
-            related_files,
+            related_files: Some(related_files),
             excerpt_ranges: ExcerptRanges {
                 editable_150: editable_range.clone(),
                 editable_180: editable_range.clone(),
@@ -3892,7 +3895,7 @@ mod tests {
             cursor_offset_in_excerpt: cursor_offset,
             excerpt_start_row: None,
             events: vec![],
-            related_files: vec![],
+            related_files: Some(vec![]),
             excerpt_ranges: ExcerptRanges {
                 editable_150: editable_range.clone(),
                 editable_180: editable_range.clone(),
@@ -4475,7 +4478,7 @@ mod tests {
             cursor_offset_in_excerpt: 30,
             excerpt_start_row: Some(0),
             events: vec![Arc::new(make_event("other.rs", "-old\n+new\n"))],
-            related_files: vec![],
+            related_files: Some(vec![]),
             excerpt_ranges: ExcerptRanges {
                 editable_150: 15..41,
                 editable_180: 15..41,
@@ -4538,7 +4541,7 @@ mod tests {
             cursor_offset_in_excerpt: 15,
             excerpt_start_row: Some(10),
             events: vec![],
-            related_files: vec![],
+            related_files: Some(vec![]),
             excerpt_ranges: ExcerptRanges {
                 editable_150: 0..28,
                 editable_180: 0..28,
@@ -4596,7 +4599,7 @@ mod tests {
             cursor_offset_in_excerpt: 25,
             excerpt_start_row: Some(0),
             events: vec![],
-            related_files: vec![],
+            related_files: Some(vec![]),
             excerpt_ranges: ExcerptRanges {
                 editable_150: editable_range.clone(),
                 editable_180: editable_range.clone(),