Fix zeta2 prompt format selection (#55338)

Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [ ] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

Closes https://github.com/zed-industries/zed/issues/52585

Release Notes:

- Fixed local zeta2 edit predictions using the wrong prompt format.
This commit is contained in:
Ben Kunkle authored and GitHub committed 2026-05-08 13:19:33 +00:00
1 parent f87f0c0e1d
commit 6bc4b4b7e4
10 files changed
+163 -38

No files matched your search

@@ -33,16 +33,16 @@ use gpui::{
};
use heapless::Vec as ArrayVec;
use language::{
Anchor, Buffer, BufferSnapshot, EditPredictionsMode, EditPreview, File, OffsetRangeExt, Point,
TextBufferSnapshot, ToOffset, ToPoint, language_settings::all_language_settings,
Anchor, Buffer, BufferSnapshot, EditPredictionPromptFormat, EditPredictionsMode, EditPreview,
File, OffsetRangeExt, Point, TextBufferSnapshot, ToOffset, ToPoint,
language_settings::all_language_settings,
};
use project::{DisableAiSettings, Project, ProjectPath, WorktreeId};
use release_channel::AppVersion;
use semver::Version;
use serde::de::DeserializeOwned;
use settings::{
EditPredictionDataCollectionChoice, EditPredictionPromptFormat, EditPredictionProvider,
Settings as _, update_settings_file,
EditPredictionDataCollectionChoice, EditPredictionProvider, Settings as _, update_settings_file,
};
use std::collections::{VecDeque, hash_map};
use std::env;
+1 -2
View File
@@ -6,10 +6,9 @@ use crate::{
use anyhow::{Context as _, Result, anyhow};
use gpui::{App, AppContext as _, Entity, Task};
use language::{
Anchor, Buffer, BufferSnapshot, ToOffset, ToPoint as _,
Anchor, Buffer, BufferSnapshot, EditPredictionPromptFormat, ToOffset, ToPoint as _,
language_settings::all_language_settings,
};
use settings::EditPredictionPromptFormat;
use std::{path::Path, sync::Arc, time::Instant};
use zeta_prompt::{ZetaPromptInput, compute_editable_and_context_ranges};
+31 -13
View File
@@ -12,11 +12,10 @@ use cloud_llm_client::{
use edit_prediction_types::PredictedCursorPosition;
use gpui::{App, AppContext as _, Entity, Task, TaskExt, WeakEntity, prelude::*};
use language::{
Buffer, BufferSnapshot, DiagnosticSeverity, OffsetRangeExt as _, ToOffset as _,
language_settings::all_language_settings, text_diff,
Buffer, BufferSnapshot, DiagnosticSeverity, EditPredictionPromptFormat, OffsetRangeExt as _,
ToOffset as _, ZetaVersion, language_settings::all_language_settings, text_diff,
};
use release_channel::AppVersion;
use settings::EditPredictionPromptFormat;
use text::{Anchor, Bias, Point};
use ui::SharedString;
use workspace::notifications::{ErrorMessagePrompt, NotificationId, show_app_notification};
@@ -101,10 +100,30 @@ pub fn request_prediction_with_zeta(
let request_task = cx.background_spawn({
async move {
let zeta_version = raw_config
let local_zeta_version = custom_server_settings
.as_ref()
.and_then(|settings| match settings.prompt_format {
EditPredictionPromptFormat::Zeta(version) => Some(version),
EditPredictionPromptFormat::Infer => {
match settings.model.to_ascii_lowercase().as_str() {
"zeta" | "zeta1" => Some(ZetaVersion::Zeta1),
"zeta2" => Some(ZetaVersion::Zeta2),
"zeta2.1" => Some(ZetaVersion::Zeta2_1),
_ => None,
}
}
_ => None,
})
.unwrap_or_default();
let zeta_format = raw_config
.as_ref()
.map(|config| config.format)
.unwrap_or(ZetaFormat::default());
.or(match local_zeta_version {
ZetaVersion::Zeta1 => None,
ZetaVersion::Zeta2 => Some(ZetaFormat::V0211SeedCoder),
ZetaVersion::Zeta2_1 => Some(ZetaFormat::V0318SeedMultiRegions),
})
.unwrap_or_default();
let cursor_offset = position.to_offset(&snapshot);
let (full_context_offset_range, prompt_input) = zeta2_prompt_input(
@@ -119,7 +138,7 @@ pub fn request_prediction_with_zeta(
repo_url,
);
let formatted_prompt = format_zeta_prompt(&prompt_input, zeta_version);
let formatted_prompt = format_zeta_prompt(&prompt_input, zeta_format);
if let Some(debug_tx) = &debug_tx {
debug_tx
@@ -139,8 +158,8 @@ pub fn request_prediction_with_zeta(
(if let Some(custom_settings) = &custom_server_settings {
let max_tokens = custom_settings.max_output_tokens * 4;
Some(match custom_settings.prompt_format {
EditPredictionPromptFormat::Zeta => {
Some(match local_zeta_version {
ZetaVersion::Zeta1 => {
let ranges = &prompt_input.excerpt_ranges;
let editable_range_in_excerpt = ranges.editable_350.clone();
let prompt = zeta1::format_zeta1_from_input(
@@ -176,11 +195,11 @@ pub fn request_prediction_with_zeta(
(request_id, parsed_output, None, None)
}
EditPredictionPromptFormat::Zeta2 => {
ZetaVersion::Zeta2 | ZetaVersion::Zeta2_1 => {
let Some(prompt) = formatted_prompt.clone() else {
return Ok((None, None));
};
let prefill = get_prefill(&prompt_input, zeta_version);
let prefill = get_prefill(&prompt_input, zeta_format);
let prompt = format!("{prompt}{prefill}");
let (response_text, request_id) = send_custom_server_request(
@@ -188,7 +207,7 @@ pub fn request_prediction_with_zeta(
custom_settings,
prompt,
max_tokens,
stop_tokens_for_format(zeta_version)
stop_tokens_for_format(zeta_format)
.iter()
.map(|token| token.to_string())
.collect(),
@@ -204,14 +223,13 @@ pub fn request_prediction_with_zeta(
let output = format!("{prefill}{response_text}");
Some(parse_zeta2_model_output(
&output,
zeta_version,
zeta_format,
&prompt_input,
)?)
};
(request_id, output_text, None, None)
}
_ => anyhow::bail!("unsupported prompt format"),
})
} else if let Some(config) = &raw_config {
let Some(prompt) = format_zeta_prompt(&prompt_input, config.format) else {
+4 -1
View File
@@ -25,7 +25,10 @@ mod toolchain;
#[cfg(test)]
pub mod buffer_tests;
pub use crate::language_settings::{AutoIndentMode, EditPredictionsMode, IndentGuideSettings};
pub use crate::language_settings::{
AutoIndentMode, EditPredictionPromptFormat, EditPredictionsMode, IndentGuideSettings,
ZetaVersion,
};
use anyhow::{Context as _, Result};
use async_trait::async_trait;
use collections::{HashMap, HashSet};
+43 -3
View File
@@ -17,7 +17,7 @@ use settings::{DocumentFoldingRanges, DocumentSymbols, IntoGpui, SemanticTokens}
pub use settings::{
AutoIndentMode, CompletionSettingsContent, EditPredictionDataCollectionChoice,
EditPredictionPromptFormat, EditPredictionProvider, EditPredictionsMode, FormatOnSave,
EditPredictionPromptFormatContent, EditPredictionProvider, EditPredictionsMode, FormatOnSave,
Formatter, FormatterList, InlayHintKind, LanguageSettingsContent, LineEndingSetting,
LspInsertMode, RewrapBehavior, ShowWhitespaceSetting, SoftWrap, WordsCompletionMode,
};
@@ -540,6 +540,46 @@ pub struct OpenAiCompatibleEditPredictionSettings {
pub prompt_format: EditPredictionPromptFormat,
}
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub enum EditPredictionPromptFormat {
#[default]
Infer,
Zeta(ZetaVersion),
CodeLlama,
StarCoder,
DeepseekCoder,
Qwen,
CodeGemma,
Codestral,
Glm,
}
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub enum ZetaVersion {
Zeta1,
Zeta2,
#[default] // NOTE: make latest version default when adding
Zeta2_1,
}
impl From<EditPredictionPromptFormatContent> for EditPredictionPromptFormat {
fn from(value: EditPredictionPromptFormatContent) -> Self {
match value {
EditPredictionPromptFormatContent::Infer => Self::Infer,
EditPredictionPromptFormatContent::Zeta => Self::Zeta(ZetaVersion::Zeta1),
EditPredictionPromptFormatContent::Zeta2 => Self::Zeta(ZetaVersion::Zeta2),
EditPredictionPromptFormatContent::Zeta2_1 => Self::Zeta(ZetaVersion::Zeta2_1),
EditPredictionPromptFormatContent::CodeLlama => Self::CodeLlama,
EditPredictionPromptFormatContent::StarCoder => Self::StarCoder,
EditPredictionPromptFormatContent::DeepseekCoder => Self::DeepseekCoder,
EditPredictionPromptFormatContent::Qwen => Self::Qwen,
EditPredictionPromptFormatContent::CodeGemma => Self::CodeGemma,
EditPredictionPromptFormatContent::Codestral => Self::Codestral,
EditPredictionPromptFormatContent::Glm => Self::Glm,
}
}
}
impl AllLanguageSettings {
/// Returns the [`LanguageSettings`] for the language with the specified name.
pub fn language<'a>(
@@ -816,7 +856,7 @@ impl settings::Settings for AllLanguageSettings {
model: model.0,
max_output_tokens: ollama.max_output_tokens.unwrap(),
api_url: ollama.api_url.unwrap().into(),
prompt_format: ollama.prompt_format.unwrap(),
prompt_format: ollama.prompt_format.unwrap().into(),
});
let openai_compatible_settings = edit_predictions.open_ai_compatible_api.unwrap();
let openai_compatible_settings = openai_compatible_settings
@@ -831,7 +871,7 @@ impl settings::Settings for AllLanguageSettings {
model,
max_output_tokens: openai_compatible_settings.max_output_tokens.unwrap(),
api_url: api_url.into(),
prompt_format: openai_compatible_settings.prompt_format.unwrap(),
prompt_format: openai_compatible_settings.prompt_format.unwrap().into(),
});
let mut file_types: FxHashMap<Arc<str>, (GlobSet, Vec<String>)> = FxHashMap::default();
+4 -3
View File
@@ -159,7 +159,7 @@ pub struct CustomEditPredictionProviderSettingsContent {
/// The prompt format to use for completions. Set to `""` to have the format be derived from the model name.
///
/// Default: ""
pub prompt_format: Option<EditPredictionPromptFormat>,
pub prompt_format: Option<EditPredictionPromptFormatContent>,
/// The name of the model.
///
/// Default: ""
@@ -185,11 +185,12 @@ pub struct CustomEditPredictionProviderSettingsContent {
strum::VariantNames,
)]
#[serde(rename_all = "snake_case")]
pub enum EditPredictionPromptFormat {
pub enum EditPredictionPromptFormatContent {
#[default]
Infer,
Zeta,
Zeta2,
Zeta2_1,
CodeLlama,
StarCoder,
DeepseekCoder,
@@ -280,7 +281,7 @@ pub struct OllamaEditPredictionSettingsContent {
/// The prompt format to use for completions. Set to `""` to have the format be derived from the model name.
///
/// Default: ""
pub prompt_format: Option<EditPredictionPromptFormat>,
pub prompt_format: Option<EditPredictionPromptFormatContent>,
}
/// Controls whether Zed collects training data when using Zed's Edit Predictions.
+1 -1
View File
@@ -504,7 +504,7 @@ fn init_renderers(cx: &mut App) {
.add_basic_renderer::<settings::AlternateScroll>(render_dropdown)
.add_basic_renderer::<settings::TerminalBlink>(render_dropdown)
.add_basic_renderer::<settings::CursorShapeContent>(render_dropdown)
.add_basic_renderer::<settings::EditPredictionPromptFormat>(render_dropdown)
.add_basic_renderer::<settings::EditPredictionPromptFormatContent>(render_dropdown)
.add_basic_renderer::<settings::EditPredictionDataCollectionChoice>(render_dropdown)
.add_basic_renderer::<f32>(render_editable_number_field)
.add_basic_renderer::<u32>(render_editable_number_field)
+10 -6
View File
@@ -5,9 +5,14 @@ use copilot::CopilotEditPredictionDelegate;
use edit_prediction::{EditPredictionModel, ZedEditPredictionDelegate};
use editor::Editor;
use gpui::{AnyWindowHandle, App, AppContext as _, Context, Entity, WeakEntity};
use language::language_settings::{EditPredictionProvider, all_language_settings};
use language::{
ZetaVersion,
language_settings::{
EditPredictionPromptFormat, EditPredictionProvider, all_language_settings,
},
};
use settings::{EditPredictionPromptFormat, SettingsStore};
use settings::SettingsStore;
use std::{cell::RefCell, rc::Rc, sync::Arc};
use ui::Window;
@@ -132,10 +137,7 @@ fn edit_prediction_provider_config_for_settings(cx: &App) -> Option<EditPredicti
}
}
if matches!(
format,
EditPredictionPromptFormat::Zeta | EditPredictionPromptFormat::Zeta2
) {
if matches!(format, EditPredictionPromptFormat::Zeta(_)) {
Some(EditPredictionProviderConfig::Zed(EditPredictionModel::Zeta))
} else {
Some(EditPredictionProviderConfig::Zed(
@@ -154,6 +156,8 @@ fn infer_prompt_format(model: &str) -> Option<EditPredictionPromptFormat> {
let model_base = model.split(':').next().unwrap_or(model);
Some(match model_base {
"zeta2" => EditPredictionPromptFormat::Zeta(ZetaVersion::Zeta2),
"zeta2.1" => EditPredictionPromptFormat::Zeta(ZetaVersion::Zeta2_1),
"codellama" | "code-llama" => EditPredictionPromptFormat::CodeLlama,
"starcoder" | "starcoder2" | "starcoderbase" => EditPredictionPromptFormat::StarCoder,
"deepseek-coder" | "deepseek-coder-v2" => EditPredictionPromptFormat::DeepseekCoder,
+2
View File
@@ -99,6 +99,7 @@ pub enum ZetaFormat {
#[default]
V0131GitMergeMarkersPrefix,
V0211Prefill,
#[serde(alias = "Zeta2")]
V0211SeedCoder,
V0331SeedCoderModelPy,
v0226Hashline,
@@ -111,6 +112,7 @@ pub enum ZetaFormat {
/// V0316, but marker numbers are relative to the cursor block (e.g. -1, -0, +1).
V0317SeedMultiRegions,
/// V0316 with larger block sizes.
#[serde(alias = "Zeta2.1")]
V0318SeedMultiRegions,
/// V0318-style markers over the full available current file excerpt with no related files.
V0327SingleFile,
+63 -5
View File
@@ -286,11 +286,29 @@ After adding your API key, Codestral will appear in the provider dropdown in the
}
```
### Self-Hosted OpenAI-compatible servers
### Local and self-hosted models
You can use any self-hosted server that implements the OpenAI completion API format. This works with vLLM, llama.cpp server, LocalAI, and other compatible servers.
You can use local or self-hosted edit prediction models through Ollama or any server that implements the OpenAI completion API format. This works with Ollama, vLLM, llama.cpp server, LocalAI, and other compatible servers.
#### Configuration
#### Ollama
Set `ollama` as your provider and configure the local model:
```json [settings]
{
"edit_predictions": {
"provider": "ollama",
"ollama": {
"api_url": "http://localhost:11434",
"model": "qwen2.5-coder:7b-base",
"prompt_format": "infer",
"max_output_tokens": 512
}
}
}
```
#### OpenAI-compatible servers
Set `open_ai_compatible_api` as your provider and configure the API endpoint:
@@ -302,7 +320,7 @@ Set `open_ai_compatible_api` as your provider and configure the API endpoint:
"api_url": "http://localhost:8080/v1/completions",
"model": "deepseek-coder-6.7b-base",
"prompt_format": "deepseek_coder",
"max_output_tokens": 64
"max_output_tokens": 512
}
}
}
@@ -310,15 +328,55 @@ Set `open_ai_compatible_api` as your provider and configure the API endpoint:
The `prompt_format` setting controls how code context is formatted for the model. Use `"infer"` to detect the format from the model name, or specify one explicitly:
- `zeta` - Zeta 1 format
- `zeta2` - Zeta 2 format
- `zeta2_1` - Zeta 2.1 format
- `code_llama` - CodeLlama format: `<PRE> prefix <SUF> suffix <MID>`
- `star_coder` - StarCoder format: `<fim_prefix>prefix<fim_suffix>suffix<fim_middle>`
- `deepseek_coder` - DeepSeek format with special unicode markers
- `qwen` - Qwen/CodeGemma format: `<|fim_prefix|>prefix<|fim_suffix|>suffix<|fim_middle|>`
- `code_gemma` - CodeGemma format: `<|fim_prefix|>prefix<|fim_suffix|>suffix<|fim_middle|>`
- `codestral` - Codestral format: `[SUFFIX]suffix[PREFIX]prefix`
- `glm` - GLM-4 format with code markers
- `infer` - Auto-detect from model name (default)
Your server must implement the OpenAI `/v1/completions` endpoint. Edit predictions will send POST requests with this format:
With `"prompt_format": "infer"`, Zed automatically uses Zeta 2 format for models named `zeta2` and Zeta 2.1 format for models named `zeta2.1`.
For example, to use Zeta 2 with Ollama:
```json [settings]
{
"edit_predictions": {
"provider": "ollama",
"ollama": {
"api_url": "http://localhost:11434",
"model": "zeta2",
"prompt_format": "infer",
"max_output_tokens": 512
}
}
}
```
To use Zeta 2.1 with an OpenAI-compatible server:
```json [settings]
{
"edit_predictions": {
"provider": "open_ai_compatible_api",
"open_ai_compatible_api": {
"api_url": "http://localhost:8080/v1/completions",
"model": "zeta2.1",
"prompt_format": "infer",
"max_output_tokens": 512
}
}
}
```
You can also set `"prompt_format": "zeta2"` or `"prompt_format": "zeta2_1"` explicitly when the model name does not match.
Your OpenAI-compatible server must implement the OpenAI `/v1/completions` endpoint. Edit predictions will send POST requests with this format:
```json
{