fix(prompt): make tool taxonomy mode-aware
This commit is contained in:
+73
-13
@@ -511,24 +511,45 @@ const TOOL_TAXONOMY_DISCOVERY: &[&str] = &["grep_files", "file_search"];
|
||||
const TOOL_TAXONOMY_GIT: &[&str] = &["git_status", "git_diff"];
|
||||
const TOOL_TAXONOMY_VERIFICATION: &[&str] = &["run_tests"];
|
||||
|
||||
fn render_core_tool_taxonomy_block() -> String {
|
||||
let core_tools = crate::core::engine::default_active_native_tool_names();
|
||||
format!(
|
||||
"## Core Tool Taxonomy\n\nUse {} for discovery. Use {} for git inspection. Use {} for verification.",
|
||||
render_core_tool_group(TOOL_TAXONOMY_DISCOVERY, core_tools),
|
||||
render_core_tool_group(TOOL_TAXONOMY_GIT, core_tools),
|
||||
render_core_tool_group(TOOL_TAXONOMY_VERIFICATION, core_tools)
|
||||
)
|
||||
fn render_core_tool_taxonomy_block(mode: AppMode) -> String {
|
||||
let core_tools = core_taxonomy_tools_for_mode(mode);
|
||||
let mut sentences = Vec::new();
|
||||
|
||||
if let Some(discovery) = render_core_tool_group(TOOL_TAXONOMY_DISCOVERY, &core_tools) {
|
||||
sentences.push(format!("Use {discovery} for discovery."));
|
||||
}
|
||||
if let Some(git) = render_core_tool_group(TOOL_TAXONOMY_GIT, &core_tools) {
|
||||
sentences.push(format!("Use {git} for git inspection."));
|
||||
}
|
||||
if let Some(verification) = render_core_tool_group(TOOL_TAXONOMY_VERIFICATION, &core_tools) {
|
||||
sentences.push(format!("Use {verification} for verification."));
|
||||
}
|
||||
|
||||
debug_assert!(
|
||||
!sentences.is_empty(),
|
||||
"core tool taxonomy has no active tool groups"
|
||||
);
|
||||
format!("## Core Tool Taxonomy\n\n{}", sentences.join(" "))
|
||||
}
|
||||
|
||||
fn render_core_tool_group(group: &[&str], core_tools: &[&str]) -> String {
|
||||
group
|
||||
fn core_taxonomy_tools_for_mode(mode: AppMode) -> Vec<&'static str> {
|
||||
let core_tools = crate::core::engine::default_active_native_tool_names();
|
||||
core_tools
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|tool| mode != AppMode::Plan || *tool != "run_tests")
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn render_core_tool_group(group: &[&str], core_tools: &[&str]) -> Option<String> {
|
||||
let rendered = group
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|tool| core_tools.contains(tool))
|
||||
.map(|tool| format!("`{tool}`"))
|
||||
.collect::<Vec<_>>()
|
||||
.join("/")
|
||||
.join("/");
|
||||
(!rendered.is_empty()).then_some(rendered)
|
||||
}
|
||||
|
||||
/// Authority recap block — appended at the end of the system prompt,
|
||||
@@ -566,7 +587,7 @@ pub fn compose_prompt_with_approval_and_model(
|
||||
approval_mode: ApprovalMode,
|
||||
model_id: &str,
|
||||
) -> String {
|
||||
let tool_taxonomy = render_core_tool_taxonomy_block();
|
||||
let tool_taxonomy = render_core_tool_taxonomy_block(mode);
|
||||
let base_prompt = apply_model_template(BASE_PROMPT.trim(), model_id);
|
||||
let parts: [&str; 5] = [
|
||||
tool_taxonomy.as_str(),
|
||||
@@ -1036,13 +1057,41 @@ mod tests {
|
||||
ApprovalMode::Suggest,
|
||||
"deepseek-v4-pro",
|
||||
);
|
||||
let expected_taxonomy = render_core_tool_taxonomy_block(AppMode::Agent);
|
||||
|
||||
assert!(
|
||||
prompt.starts_with("## Core Tool Taxonomy\n\nUse `grep_files`/`file_search` for discovery. Use `git_status`/`git_diff` for git inspection. Use `run_tests` for verification."),
|
||||
prompt.starts_with(&expected_taxonomy),
|
||||
"composed prompt should start with the compact generated tool taxonomy"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plan_prompt_taxonomy_omits_run_tests() {
|
||||
let prompt = compose_prompt_with_approval_and_model(
|
||||
AppMode::Plan,
|
||||
Personality::Calm,
|
||||
ApprovalMode::Never,
|
||||
"deepseek-v4-pro",
|
||||
);
|
||||
let expected_taxonomy = render_core_tool_taxonomy_block(AppMode::Plan);
|
||||
|
||||
assert!(
|
||||
prompt.starts_with(&expected_taxonomy),
|
||||
"Plan prompt should start with its mode-specific tool taxonomy"
|
||||
);
|
||||
assert!(
|
||||
expected_taxonomy.contains("for discovery")
|
||||
&& expected_taxonomy.contains("for git inspection"),
|
||||
"Plan taxonomy should keep read-only discovery and git guidance"
|
||||
);
|
||||
assert!(
|
||||
!expected_taxonomy.contains("run_tests")
|
||||
&& !expected_taxonomy.contains("for verification")
|
||||
&& !expected_taxonomy.contains("Use "),
|
||||
"Plan taxonomy must not advertise unavailable verification tools: {expected_taxonomy:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn core_tool_taxonomy_only_references_default_active_tools() {
|
||||
let core_tools = crate::core::engine::default_active_native_tool_names();
|
||||
@@ -1402,6 +1451,17 @@ mod tests {
|
||||
!text.contains("Reforço de Idioma"),
|
||||
"English locale must not get a pt-BR closer: {text:?}"
|
||||
);
|
||||
assert!(
|
||||
!contains_cjk(BASE_PROMPT),
|
||||
"base prompt must not contain static CJK priming tokens"
|
||||
);
|
||||
for mode in [AppMode::Agent, AppMode::Plan, AppMode::Yolo] {
|
||||
let taxonomy = render_core_tool_taxonomy_block(mode);
|
||||
assert!(
|
||||
!contains_cjk(&taxonomy),
|
||||
"tool taxonomy must not contain static CJK priming tokens: {taxonomy:?}"
|
||||
);
|
||||
}
|
||||
// Do not assert on arbitrary CJK in the full system prompt: project
|
||||
// context may legitimately contain localized file names, README text,
|
||||
// or user-authored instructions. The locale bookend markers above are
|
||||
|
||||
Reference in New Issue
Block a user