Qwen prompt format (#59150)

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 adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

Closes #ISSUE

Release Notes:

- N/A or Added/Fixed/Improved ...

---------

Co-authored-by: zed-zippy[bot] <234243425+zed-zippy[bot]@users.noreply.github.com>
This commit is contained in:
Ben Kunkleandzed-zippy[bot] authored and GitHub committed 2026-06-16 17:29:30 +00:00
1 parent 47dd63518c
commit 2026946c10
1 file changed
+189 -4
+189 -4
View File
@@ -118,6 +118,8 @@ pub enum ZetaFormat {
V0327SingleFile, V0327SingleFile,
/// V0318-style prompt with buffer diagnostics /// V0318-style prompt with buffer diagnostics
V0420Diagnostics, V0420Diagnostics,
/// V0318-style multi-region format using Qwen FIM tokens and PSM ordering.
V0608QwenMultiRegions,
} }
impl std::fmt::Display for ZetaFormat { impl std::fmt::Display for ZetaFormat {
@@ -284,7 +286,8 @@ pub fn format_zeta_prompt(input: &ZetaPromptInput, format: ZetaFormat) -> Option
| ZetaFormat::V0316SeedMultiRegions | ZetaFormat::V0316SeedMultiRegions
| ZetaFormat::V0317SeedMultiRegions | ZetaFormat::V0317SeedMultiRegions
| ZetaFormat::V0331SeedCoderModelPy | ZetaFormat::V0331SeedCoderModelPy
| ZetaFormat::V0318SeedMultiRegions => 4096, | ZetaFormat::V0318SeedMultiRegions
| ZetaFormat::V0608QwenMultiRegions => 4096,
ZetaFormat::V0420Diagnostics => 8192, ZetaFormat::V0420Diagnostics => 8192,
ZetaFormat::V0327SingleFile => 16384, ZetaFormat::V0327SingleFile => 16384,
}; };
@@ -330,6 +333,18 @@ pub fn special_tokens_for_format(format: ZetaFormat) -> &'static [&'static str]
]; ];
TOKENS TOKENS
} }
ZetaFormat::V0608QwenMultiRegions => {
static TOKENS: &[&str] = &[
qwen::FIM_PREFIX,
qwen::FIM_SUFFIX,
qwen::FIM_MIDDLE,
qwen::FILE_MARKER,
qwen::END_MARKER,
CURSOR_MARKER,
multi_region::MARKER_TAG_PREFIX,
];
TOKENS
}
ZetaFormat::V0317SeedMultiRegions => { ZetaFormat::V0317SeedMultiRegions => {
static TOKENS: &[&str] = &[ static TOKENS: &[&str] = &[
seed_coder::FIM_SUFFIX, seed_coder::FIM_SUFFIX,
@@ -386,6 +401,7 @@ pub fn token_limits_for_format(format: ZetaFormat) -> (usize, usize) {
| ZetaFormat::V0316SeedMultiRegions | ZetaFormat::V0316SeedMultiRegions
| ZetaFormat::V0318SeedMultiRegions | ZetaFormat::V0318SeedMultiRegions
| ZetaFormat::V0420Diagnostics | ZetaFormat::V0420Diagnostics
| ZetaFormat::V0608QwenMultiRegions
| ZetaFormat::V0317SeedMultiRegions | ZetaFormat::V0317SeedMultiRegions
| ZetaFormat::V0327SingleFile | ZetaFormat::V0327SingleFile
| ZetaFormat::V0304SeedNoEdits => (350, 150), | ZetaFormat::V0304SeedNoEdits => (350, 150),
@@ -412,6 +428,7 @@ pub fn stop_tokens_for_format(format: ZetaFormat) -> &'static [&'static str] {
ZetaFormat::V0318SeedMultiRegions | ZetaFormat::V0420Diagnostics => { ZetaFormat::V0318SeedMultiRegions | ZetaFormat::V0420Diagnostics => {
&[multi_region::V0318_END_MARKER] &[multi_region::V0318_END_MARKER]
} }
ZetaFormat::V0608QwenMultiRegions => &[qwen::END_MARKER],
ZetaFormat::V0317SeedMultiRegions => &[multi_region::V0317_END_MARKER], ZetaFormat::V0317SeedMultiRegions => &[multi_region::V0317_END_MARKER],
ZetaFormat::V0327SingleFile => &[multi_region::V0327_END_MARKER], ZetaFormat::V0327SingleFile => &[multi_region::V0327_END_MARKER],
} }
@@ -447,6 +464,10 @@ pub fn training_delimiters_for_format(format: ZetaFormat) -> TrainingDelimiters
instruction_part: seed_coder::FIM_SUFFIX, instruction_part: seed_coder::FIM_SUFFIX,
response_part: seed_coder::FIM_MIDDLE, response_part: seed_coder::FIM_MIDDLE,
}, },
ZetaFormat::V0608QwenMultiRegions => TrainingDelimiters {
instruction_part: qwen::FIM_PREFIX,
response_part: qwen::FIM_MIDDLE,
},
ZetaFormat::V0112MiddleAtEnd ZetaFormat::V0112MiddleAtEnd
| ZetaFormat::V0113Ordered | ZetaFormat::V0113Ordered
| ZetaFormat::V0114180EditableRegion => TrainingDelimiters { | ZetaFormat::V0114180EditableRegion => TrainingDelimiters {
@@ -497,7 +518,8 @@ pub fn excerpt_ranges_for_format(
| ZetaFormat::V0316SeedMultiRegions | ZetaFormat::V0316SeedMultiRegions
| ZetaFormat::V0318SeedMultiRegions | ZetaFormat::V0318SeedMultiRegions
| ZetaFormat::V0317SeedMultiRegions | ZetaFormat::V0317SeedMultiRegions
| ZetaFormat::V0420Diagnostics => ( | ZetaFormat::V0420Diagnostics
| ZetaFormat::V0608QwenMultiRegions => (
ranges.editable_350.clone(), ranges.editable_350.clone(),
ranges.editable_350_context_150.clone(), ranges.editable_350_context_150.clone(),
), ),
@@ -602,6 +624,16 @@ pub fn write_cursor_excerpt_section_for_format(
context, context,
editable_range, editable_range,
cursor_offset, cursor_offset,
seed_coder::FILE_MARKER,
));
}
ZetaFormat::V0608QwenMultiRegions => {
prompt.push_str(&build_v0318_cursor_prefix(
path,
context,
editable_range,
cursor_offset,
qwen::FILE_MARKER,
)); ));
} }
ZetaFormat::V0317SeedMultiRegions => { ZetaFormat::V0317SeedMultiRegions => {
@@ -618,6 +650,7 @@ pub fn write_cursor_excerpt_section_for_format(
context, context,
editable_range, editable_range,
cursor_offset, cursor_offset,
seed_coder::FILE_MARKER,
)); ));
} }
} }
@@ -684,10 +717,11 @@ fn build_v0318_cursor_prefix(
context: &str, context: &str,
editable_range: &Range<usize>, editable_range: &Range<usize>,
cursor_offset: usize, cursor_offset: usize,
file_marker: &str,
) -> String { ) -> String {
let mut section = String::new(); let mut section = String::new();
let path_str = path.to_string_lossy(); let path_str = path.to_string_lossy();
write!(section, "{}{}\n", seed_coder::FILE_MARKER, path_str).ok(); write!(section, "{}{}\n", file_marker, path_str).ok();
section.push_str(&context[..editable_range.start]); section.push_str(&context[..editable_range.start]);
@@ -846,6 +880,27 @@ pub fn format_prompt_with_budget_for_format(
budget_with_margin, budget_with_margin,
) )
} }
ZetaFormat::V0608QwenMultiRegions => {
let mut cursor_section = String::new();
write_cursor_excerpt_section_for_format(
format,
&mut cursor_section,
path,
context,
&editable_range,
cursor_offset,
);
qwen::assemble_fim_prompt(
context,
&editable_range,
&cursor_section,
&input.events,
related_files,
apply_prompt_budget_margin(max_tokens),
)
}
ZetaFormat::V0327SingleFile => { ZetaFormat::V0327SingleFile => {
let mut cursor_section = String::new(); let mut cursor_section = String::new();
write_cursor_excerpt_section_for_format( write_cursor_excerpt_section_for_format(
@@ -1015,6 +1070,7 @@ pub fn max_edit_event_count_for_format(format: &ZetaFormat) -> usize {
| ZetaFormat::V0318SeedMultiRegions | ZetaFormat::V0318SeedMultiRegions
| ZetaFormat::V0317SeedMultiRegions | ZetaFormat::V0317SeedMultiRegions
| ZetaFormat::V0420Diagnostics | ZetaFormat::V0420Diagnostics
| ZetaFormat::V0608QwenMultiRegions
| ZetaFormat::V0327SingleFile => 6, | ZetaFormat::V0327SingleFile => 6,
} }
} }
@@ -1041,6 +1097,7 @@ pub fn get_prefill_for_format(
| ZetaFormat::V0318SeedMultiRegions | ZetaFormat::V0318SeedMultiRegions
| ZetaFormat::V0317SeedMultiRegions | ZetaFormat::V0317SeedMultiRegions
| ZetaFormat::V0420Diagnostics | ZetaFormat::V0420Diagnostics
| ZetaFormat::V0608QwenMultiRegions
| ZetaFormat::V0327SingleFile => String::new(), | ZetaFormat::V0327SingleFile => String::new(),
} }
} }
@@ -1057,6 +1114,7 @@ pub fn output_end_marker_for_format(format: ZetaFormat) -> Option<&'static str>
ZetaFormat::V0316SeedMultiRegions => Some(multi_region::V0316_END_MARKER), ZetaFormat::V0316SeedMultiRegions => Some(multi_region::V0316_END_MARKER),
ZetaFormat::V0318SeedMultiRegions => Some(multi_region::V0318_END_MARKER), ZetaFormat::V0318SeedMultiRegions => Some(multi_region::V0318_END_MARKER),
ZetaFormat::V0420Diagnostics => Some(multi_region::V0318_END_MARKER), ZetaFormat::V0420Diagnostics => Some(multi_region::V0318_END_MARKER),
ZetaFormat::V0608QwenMultiRegions => Some(qwen::END_MARKER),
ZetaFormat::V0317SeedMultiRegions => Some(multi_region::V0317_END_MARKER), ZetaFormat::V0317SeedMultiRegions => Some(multi_region::V0317_END_MARKER),
ZetaFormat::V0327SingleFile => Some(multi_region::V0327_END_MARKER), ZetaFormat::V0327SingleFile => Some(multi_region::V0327_END_MARKER),
@@ -1118,6 +1176,19 @@ pub fn encode_patch_as_output_for_format(
Ok(None) Ok(None)
} }
} }
ZetaFormat::V0608QwenMultiRegions => {
let empty_patch = patch.lines().count() <= 3;
if empty_patch {
let marker_offsets =
multi_region::compute_marker_offsets_v0318(old_editable_region);
let marker_num =
multi_region::nearest_marker_number(cursor_offset, &marker_offsets);
let tag = multi_region::marker_tag(marker_num);
Ok(Some(format!("{tag}{tag}{}", qwen::END_MARKER)))
} else {
Ok(None)
}
}
ZetaFormat::V0317SeedMultiRegions => { ZetaFormat::V0317SeedMultiRegions => {
let empty_patch = patch.lines().count() <= 3; let empty_patch = patch.lines().count() <= 3;
if empty_patch { if empty_patch {
@@ -1201,6 +1272,18 @@ pub fn format_expected_output(
multi_region::V0318_END_MARKER, multi_region::V0318_END_MARKER,
) )
} }
ZetaFormat::V0608QwenMultiRegions => {
let (new_editable, first_hunk_offset) =
udiff::apply_diff_to_string_with_hunk_offset(patch, &old_editable)?;
let cursor_in_new = cursor_in_new_text(cursor_offset, first_hunk_offset, &new_editable);
multi_region::encode_from_old_and_new_v0318(
&old_editable,
&new_editable,
cursor_in_new,
CURSOR_MARKER,
qwen::END_MARKER,
)
}
ZetaFormat::V0327SingleFile => { ZetaFormat::V0327SingleFile => {
let (new_editable, first_hunk_offset) = let (new_editable, first_hunk_offset) =
udiff::apply_diff_to_string_with_hunk_offset(patch, &old_editable)?; udiff::apply_diff_to_string_with_hunk_offset(patch, &old_editable)?;
@@ -1362,7 +1445,9 @@ pub fn parse_zeta2_model_output(
editable_range_in_context, editable_range_in_context,
multi_region::apply_marker_span_v0316(old_editable_region, output)?, multi_region::apply_marker_span_v0316(old_editable_region, output)?,
), ),
ZetaFormat::V0318SeedMultiRegions | ZetaFormat::V0420Diagnostics => ( ZetaFormat::V0318SeedMultiRegions
| ZetaFormat::V0420Diagnostics
| ZetaFormat::V0608QwenMultiRegions => (
editable_range_in_context, editable_range_in_context,
multi_region::apply_marker_span_v0318(old_editable_region, output)?, multi_region::apply_marker_span_v0318(old_editable_region, output)?,
), ),
@@ -3432,6 +3517,74 @@ pub mod hashline {
} }
} }
pub mod qwen {
use super::*;
pub const FIM_PREFIX: &str = "<|fim_prefix|>";
pub const FIM_SUFFIX: &str = "<|fim_suffix|>";
pub const FIM_MIDDLE: &str = "<|fim_middle|>";
pub const FILE_MARKER: &str = "<|file_sep|>";
pub const END_MARKER: &str = "<|im_end|>";
pub fn assemble_fim_prompt(
context: &str,
editable_range: &Range<usize>,
cursor_prefix_section: &str,
events: &[Arc<Event>],
related_files: &[RelatedFile],
max_tokens: usize,
) -> String {
let suffix_section = build_suffix_section(context, editable_range);
let cursor_prefix_tokens = estimate_tokens(cursor_prefix_section.len() + FIM_PREFIX.len());
let suffix_tokens =
estimate_tokens(suffix_section.len() + FIM_SUFFIX.len() + FIM_MIDDLE.len());
let budget_after_cursor = max_tokens.saturating_sub(cursor_prefix_tokens + suffix_tokens);
let edit_history_section = super::format_edit_history_within_budget(
events,
FILE_MARKER,
"edit_history",
budget_after_cursor,
max_edit_event_count_for_format(&ZetaFormat::V0608QwenMultiRegions),
);
let edit_history_tokens = estimate_tokens(edit_history_section.len() + "\n".len());
let budget_after_edit_history = budget_after_cursor.saturating_sub(edit_history_tokens);
let related_files_section = super::format_related_files_within_budget(
related_files,
FILE_MARKER,
"",
budget_after_edit_history,
);
let mut prompt = String::new();
prompt.push_str(FIM_PREFIX);
prompt.push_str(&related_files_section);
if !related_files_section.is_empty() {
prompt.push('\n');
}
prompt.push_str(&edit_history_section);
if !edit_history_section.is_empty() {
prompt.push('\n');
}
prompt.push_str(cursor_prefix_section);
prompt.push_str(&suffix_section);
prompt.push_str(FIM_MIDDLE);
prompt
}
fn build_suffix_section(context: &str, editable_range: &Range<usize>) -> String {
let mut section = String::new();
section.push_str(FIM_SUFFIX);
section.push_str(&context[editable_range.end..]);
if !section.ends_with('\n') {
section.push('\n');
}
section
}
}
pub mod seed_coder { pub mod seed_coder {
//! Seed-Coder prompt format using SPM (Suffix-Prefix-Middle) FIM mode. //! Seed-Coder prompt format using SPM (Suffix-Prefix-Middle) FIM mode.
//! //!
@@ -5383,6 +5536,38 @@ mod tests {
); );
} }
#[test]
fn test_qwen36_multi_region_uses_qwen_psm_fim_format() {
let input = make_input(
"prefix\neditable\nsuffix",
7..15,
10,
vec![make_event("a.rs", "-old\n+new\n")],
vec![make_related_file("related.rs", "fn helper() {}\n")],
);
assert_eq!(
format_prompt_with_budget_for_format(&input, ZetaFormat::V0608QwenMultiRegions, 10000)
.expect("qwen prompt formatting should succeed"),
indoc! {r#"
<|fim_prefix|><|file_sep|>related.rs
fn helper() {}
<|file_sep|>edit_history
--- a/a.rs
+++ b/a.rs
-old
+new
<|file_sep|>test.rs
prefix
<|marker_1|>edi<|user_cursor|>table<|marker_2|>
<|fim_suffix|>
suffix
<|fim_middle|>"#}
);
}
#[test] #[test]
fn test_v0420_formats_diagnostics_before_related_files() { fn test_v0420_formats_diagnostics_before_related_files() {
let mut input = make_input( let mut input = make_input(