feat(prompt): add core tool taxonomy block
This commit is contained in:
@@ -2085,6 +2085,10 @@ mod tool_execution;
|
||||
mod tool_setup;
|
||||
mod turn_loop;
|
||||
|
||||
pub(crate) fn default_active_native_tool_names() -> &'static [&'static str] {
|
||||
tool_catalog::DEFAULT_ACTIVE_NATIVE_TOOLS
|
||||
}
|
||||
|
||||
use self::approval::{ApprovalDecision, ApprovalResult, UserInputDecision};
|
||||
#[cfg(test)]
|
||||
use self::dispatch::should_parallelize_tool_batch;
|
||||
|
||||
+69
-11
@@ -2,7 +2,7 @@
|
||||
//! System prompts for different modes.
|
||||
//!
|
||||
//! Prompts are assembled from composable layers loaded at compile time:
|
||||
//! base.md → personality overlay → mode delta → approval policy
|
||||
//! tool taxonomy → base.md → personality overlay → mode delta → approval policy
|
||||
//!
|
||||
//! This keeps each concern in its own file and makes prompt tuning
|
||||
//! a single-file operation.
|
||||
@@ -490,10 +490,11 @@ fn approval_prompt_for_mode(mode: AppMode, approval_mode: ApprovalMode) -> &'sta
|
||||
}
|
||||
|
||||
/// Compose the full system prompt in deterministic order:
|
||||
/// 1. base.md — core identity, toolbox, execution contract
|
||||
/// 2. personality — voice and tone overlay
|
||||
/// 3. mode delta — mode-specific permissions and workflow
|
||||
/// 4. approval policy — tool-approval behavior
|
||||
/// 1. tool taxonomy — compact hints generated from the eager core tools
|
||||
/// 2. base.md — core identity, toolbox, execution contract
|
||||
/// 3. personality — voice and tone overlay
|
||||
/// 4. mode delta — mode-specific permissions and workflow
|
||||
/// 5. approval policy — tool-approval behavior
|
||||
///
|
||||
/// Each layer is separated by a blank line for readability in the
|
||||
/// rendered prompt (the model sees them as contiguous sections).
|
||||
@@ -506,6 +507,30 @@ fn apply_model_template(prompt: &str, model_id: &str) -> String {
|
||||
prompt.replace("{model_id}", model_id)
|
||||
}
|
||||
|
||||
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_group(group: &[&str], core_tools: &[&str]) -> String {
|
||||
group
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|tool| core_tools.contains(tool))
|
||||
.map(|tool| format!("`{tool}`"))
|
||||
.collect::<Vec<_>>()
|
||||
.join("/")
|
||||
}
|
||||
|
||||
/// Authority recap block — appended at the end of the system prompt,
|
||||
/// just before the user's first message. Uses recency bias constructively:
|
||||
/// this is the last thing the model reads before generating, so it
|
||||
@@ -541,8 +566,11 @@ pub fn compose_prompt_with_approval_and_model(
|
||||
approval_mode: ApprovalMode,
|
||||
model_id: &str,
|
||||
) -> String {
|
||||
let parts: [&str; 4] = [
|
||||
&apply_model_template(BASE_PROMPT.trim(), model_id),
|
||||
let tool_taxonomy = render_core_tool_taxonomy_block();
|
||||
let base_prompt = apply_model_template(BASE_PROMPT.trim(), model_id);
|
||||
let parts: [&str; 5] = [
|
||||
tool_taxonomy.as_str(),
|
||||
base_prompt.as_str(),
|
||||
personality.prompt().trim(),
|
||||
mode_prompt(mode).trim(),
|
||||
approval_prompt_for_mode(mode, approval_mode).trim(),
|
||||
@@ -1000,6 +1028,36 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn composed_prompt_starts_with_core_tool_taxonomy() {
|
||||
let prompt = compose_prompt_with_approval_and_model(
|
||||
AppMode::Agent,
|
||||
Personality::Calm,
|
||||
ApprovalMode::Suggest,
|
||||
"deepseek-v4-pro",
|
||||
);
|
||||
|
||||
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."),
|
||||
"composed prompt should start with the compact generated tool taxonomy"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn core_tool_taxonomy_only_references_default_active_tools() {
|
||||
let core_tools = crate::core::engine::default_active_native_tool_names();
|
||||
for tool in TOOL_TAXONOMY_DISCOVERY
|
||||
.iter()
|
||||
.chain(TOOL_TAXONOMY_GIT)
|
||||
.chain(TOOL_TAXONOMY_VERIFICATION)
|
||||
{
|
||||
assert!(
|
||||
core_tools.contains(tool),
|
||||
"tool taxonomy references {tool}, but it is not in the eager native-tool list"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn authority_recap_appears_in_full_prompt() {
|
||||
let tmp = tempdir().expect("tempdir");
|
||||
@@ -1344,10 +1402,10 @@ mod tests {
|
||||
!text.contains("Reforço de Idioma"),
|
||||
"English locale must not get a pt-BR closer: {text:?}"
|
||||
);
|
||||
assert!(
|
||||
!contains_cjk(&text),
|
||||
"English system prompt should avoid native-script priming tokens: {text:?}"
|
||||
);
|
||||
// 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
|
||||
// the priming tokens this test is meant to guard.
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user