修复+重构: 全库走查真bug+架构+P1/P2 后端 crate
- df-nodes: schema required 对齐 + docker POSIX 注入防御 + HumanNode timeout 1800 + parse_review_json(verdict规范/score clamp/正则兜底) - df-mcp: update 实体校验(防跨实体 B-260801-01) - df-storage: keyring 迁移失败达阈值清除明文 - df-ai: router estimated_context+tier tiebreak+DataReadOnly 兜底 + sanitize step4 显式不制造 orphan - df-ideas: adversarial tier:None 对齐
This commit is contained in:
+137
-32
@@ -8,8 +8,9 @@
|
||||
//! 1. `IntentRecognizer::recognize(message)` —— 规则/关键词匹配(方式 A,零延迟零成本)
|
||||
//! 返回 `(Intent, f32)`,置信度 0.0–1.0。低置信 → 上游 fallback 全量工具。
|
||||
//! 2. `tool_subset_for(intent)` —— 硬编码工具名→domain 映射,工具名子集(空 = 全量 fallback)。
|
||||
//! 3. `suggested_model_tier(intent)` —— 模态建议**接口预留**,当前恒返 `None`
|
||||
//! (待模型模态管理 Phase 落地后补充实际逻辑)。
|
||||
//! 3. `suggested_model_tier(intent)` —— 模型模态档位建议(意图 → `ModelTier` 映射):
|
||||
//! Code/Debug/Http → Heavy,File/Search/Task/Idea/Project/Conversation → Standard,
|
||||
//! Chat → Fast,Unknown → None(不强加偏好)。router 同 weight tiebreak + 模型路由用。
|
||||
//!
|
||||
//! ## 设计原则
|
||||
//! - **不碰** `tool_registry`:domain 映射在本文件内硬编码工具名常量,运行期不读 registry。
|
||||
@@ -70,12 +71,12 @@ impl Intent {
|
||||
}
|
||||
}
|
||||
|
||||
// ---- ModelTier 预留 ---------------------------------------------------------
|
||||
// ---- ModelTier 档位 ---------------------------------------------------------
|
||||
|
||||
/// 模型模态档位(**预留**)。
|
||||
/// 模型模态档位。
|
||||
///
|
||||
/// 待模型模态管理 Phase 落地后定义实际 provider/model 映射。
|
||||
/// 当前仅占位于 `suggested_model_tier` 返回类型,逻辑恒返 `None`。
|
||||
/// `suggested_model_tier` 据 Intent 映射到此档位,供 router 同 weight tiebreak
|
||||
/// (重档位优先)或后续 provider/model 路由(待模型模态管理 Phase 接入)使用。
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ModelTier {
|
||||
/// 轻量快速(简单意图/闲聊)
|
||||
@@ -230,8 +231,14 @@ const GENERIC_GROUP: &[IntentGroup] = &[
|
||||
/// 本表硬编码,不读 registry 运行期状态(保持模块独立可单测)。
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum ToolDomain {
|
||||
/// 数据/业务:项目/任务/灵感/工作流/回收站
|
||||
/// 数据/业务:项目/任务/灵感/工作流/回收站(全量,含写工具)
|
||||
Data,
|
||||
/// 数据只读子集:list_*/get_*_count(不含 create/update/delete/advance/run_workflow/restore/purge/bind_directory)
|
||||
///
|
||||
/// 用途:`Intent::Code` 收敛工具时,既保留编码场景用户常需要的"看一下项目/任务结构"只读
|
||||
/// 探索工具(list_projects/list_tasks/list_ideas/list_trash),又不放大暴露面(不暴露写工具,
|
||||
/// 对齐 `Intent::Code` 不含 `Intent::Debug` 才有的 run_command 那类收紧设计)。
|
||||
DataReadOnly,
|
||||
/// 文件:读写/patch/列目录/搜索(不含命令执行)
|
||||
File,
|
||||
/// 命令执行:run_command(shell 命令,独立 domain 防止被泛 File 意图带出)
|
||||
@@ -280,6 +287,20 @@ impl ToolDomain {
|
||||
// Code/File/Search 不带 → 减少 LLM 对 run_command 的偏好暴露。
|
||||
ToolDomain::Exec => &["run_command"],
|
||||
ToolDomain::Http => &["http_request"],
|
||||
// DataReadOnly:Data domain 的只读子集。
|
||||
// 源于 Code 意图收敛需求:用户在编码场景说"先 list_projects 看下项目结构"
|
||||
// "create_task 记一下"时,意图识别可能命中 Code(SPECIFIC > ENTITY),原 Code subset
|
||||
// 不含 Data domain → list_projects/create_task 对 LLM 不可见,agent 被迫反复 read_file。
|
||||
// 加 Data 全 domain 会暴露 create/update/delete 等写工具(放大暴露面),
|
||||
// 故取只读子集:list_*/get_*_count(纯查询,无副作用)。
|
||||
ToolDomain::DataReadOnly => &[
|
||||
"list_projects",
|
||||
"list_tasks",
|
||||
"list_ideas",
|
||||
"list_trash",
|
||||
"get_project_count",
|
||||
"get_task_count",
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -416,14 +437,17 @@ fn best_in_group(message: &str, group: &[IntentGroup]) -> Option<(Intent, f32)>
|
||||
/// 返回空 `Vec` 表示该意图**无工具收敛**(Chat)或**未识别**(Unknown),
|
||||
/// 上游应走**全量 fallback**(即不过滤工具,交全量给 LLM)。
|
||||
///
|
||||
/// 设计:Code → [file, http];File → [file];Project/Task/Idea → [data, file](加 file 防「提项目/任务 → 误判 → 砍只读探索」,见 L1);
|
||||
/// 设计:Code → [file, http, data_readonly](加 data_readonly:编码场景用户常需"先 list_projects
|
||||
/// 看下结构/list_tasks 记一下",Code 命中优先级高于 Project/Task(SPECIFIC > ENTITY)会砍 Data domain,
|
||||
/// 取只读子集防"断手"又不放大写工具暴露面,见 ToolDomain::DataReadOnly);
|
||||
/// File → [file];Project/Task/Idea → [data, file](加 file 防「提项目/任务 → 误判 → 砍只读探索」,见 L1);
|
||||
/// Http → [http];Search → [file](含 search_files);Conversation → [];
|
||||
/// Chat → [];Debug → [file, exec, http, data](调试常需跑命令+读文件+查 API+查任务/工作流状态,CR-25 审查🟡-1 加 data 防"调试任务"丢 Data 工具);
|
||||
/// **仅 Debug 含 Exec**(用户明确"运行/测试/构建/调试"才暴露 run_command),
|
||||
/// Code/File/Search 不含 Exec → 收紧 run_command 暴露面;Unknown → [](全量)。
|
||||
pub fn tool_subset_for(intent: &Intent) -> Vec<&'static str> {
|
||||
let domains: &[ToolDomain] = match intent {
|
||||
Intent::Code => &[ToolDomain::File, ToolDomain::Http],
|
||||
Intent::Code => &[ToolDomain::File, ToolDomain::Http, ToolDomain::DataReadOnly],
|
||||
Intent::Debug => &[ToolDomain::File, ToolDomain::Exec, ToolDomain::Http, ToolDomain::Data],
|
||||
Intent::File => &[ToolDomain::File],
|
||||
// Project/Task/Idea 加 File:用户提"项目/任务"时常是在其内编码/排查
|
||||
@@ -449,19 +473,36 @@ pub fn tool_subset_for(intent: &Intent) -> Vec<&'static str> {
|
||||
out
|
||||
}
|
||||
|
||||
// ---- 模态建议(接口预留) ---------------------------------------------------
|
||||
// ---- 模态建议(意图 → 模型 tier 映射) ----------------------------------------
|
||||
|
||||
/// 按 Intent 建议模型模态档位。
|
||||
/// 按 Intent 建议模型模态档位(意图 → 模型 tier 的语义映射)。
|
||||
///
|
||||
/// **预留接口**:当前恒返 `None`。待模型模态管理 Phase 落地后补充:
|
||||
/// - Chat/Conversation → `Fast`
|
||||
/// - Code/File/Task/Idea/Search → `Standard`
|
||||
/// - Debug/Http(复杂排查/多跳调用)→ `Heavy`
|
||||
/// **映射分组**(对齐 router 同 weight tiebreak 缺语义的根因修复):
|
||||
/// - `Code`/`Debug` → `Heavy`(复杂推理/重构/排查,需重模型)
|
||||
/// - `Http` → `Heavy`(多跳外部调用,链长易错,需重模型把关)
|
||||
/// - `File`/`Search`/`Task`/`Idea`/`Project`/`Conversation` → `Standard`(默认复杂度)
|
||||
/// - `Chat` → `Fast`(简单闲聊,轻量即可)
|
||||
/// - `Unknown` → `None`(未识别,fallback 全量工具时不强加 tier 偏好,
|
||||
/// 上游走默认档位)
|
||||
///
|
||||
/// 返回 `None` 时上游应使用默认档位(待模态管理 Phase 定义)。
|
||||
pub fn suggested_model_tier(_intent: &Intent) -> Option<ModelTier> {
|
||||
// TODO(model-tier-phase): 待模型模态管理落地后填实映射。
|
||||
None
|
||||
/// 返回 `Some(ModelTier)` 时上游可作为同 weight 候选间的 tiebreak 依据
|
||||
/// (重档位优先),或据此路由到不同 provider/model(待模型模态管理 Phase 接入)。
|
||||
pub fn suggested_model_tier(intent: &Intent) -> Option<ModelTier> {
|
||||
match intent {
|
||||
// 重型:复杂推理/重构/排查/多跳调用
|
||||
Intent::Code | Intent::Debug | Intent::Http => Some(ModelTier::Heavy),
|
||||
// 标准:默认复杂度
|
||||
Intent::File
|
||||
| Intent::Search
|
||||
| Intent::Task
|
||||
| Intent::Idea
|
||||
| Intent::Project
|
||||
| Intent::Conversation => Some(ModelTier::Standard),
|
||||
// 轻量:简单闲聊
|
||||
Intent::Chat => Some(ModelTier::Fast),
|
||||
// 未识别:不强加 tier 偏好,fallback 上游默认
|
||||
Intent::Unknown => None,
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 工具子集过滤(agentic loop 接入用,改进2 A) -----------------------------
|
||||
@@ -813,7 +854,55 @@ mod tests {
|
||||
assert!(s.contains(&"write_file"));
|
||||
assert!(s.contains(&"patch_file"));
|
||||
assert!(s.contains(&"http_request"));
|
||||
assert!(!s.contains(&"list_projects"));
|
||||
// Code 现含 DataReadOnly → list_projects 等只读工具保留(不再断言"不含")
|
||||
assert!(s.contains(&"list_projects"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subset_code_keeps_data_readonly_tools() {
|
||||
// 根因修复(Code 命中砍 Data domain 致 list_*/get_*_count 不可见):
|
||||
// Code subset 应含 DataReadOnly 全部 6 个只读工具,让"先 list_projects 看下结构"
|
||||
// "list_tasks 记一下"这类编码场景的口语不被 Code 意图砍工具。
|
||||
let s = tool_subset_for(&Intent::Code);
|
||||
for read_only in [
|
||||
"list_projects",
|
||||
"list_tasks",
|
||||
"list_ideas",
|
||||
"list_trash",
|
||||
"get_project_count",
|
||||
"get_task_count",
|
||||
] {
|
||||
assert!(
|
||||
s.contains(&read_only),
|
||||
"Code subset 应含只读工具 {}(DataReadOnly domain)",
|
||||
read_only
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subset_code_no_data_write_tools() {
|
||||
// 防回归:Code subset 只加 Data 的"只读子集",不得暴露写工具。
|
||||
// create_task 不应在 Code subset(只读 list_* 在,写 create_* 不在)。
|
||||
// create_project/update_project/delete_project/advance_task/run_workflow 同理。
|
||||
let s = tool_subset_for(&Intent::Code);
|
||||
for write_tool in [
|
||||
"create_task",
|
||||
"update_task",
|
||||
"delete_task",
|
||||
"create_project",
|
||||
"update_project",
|
||||
"delete_project",
|
||||
"advance_task",
|
||||
"run_workflow",
|
||||
] {
|
||||
assert!(
|
||||
!s.contains(&write_tool),
|
||||
"Code subset 不应含写工具 {}(只读子集,防放大暴露面), 实际 subset: {:?}",
|
||||
write_tool,
|
||||
s
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -907,26 +996,39 @@ mod tests {
|
||||
assert_eq!(ToolDomain::Http.tools(), &["http_request"]);
|
||||
}
|
||||
|
||||
// --- suggested_model_tier 预留 ---
|
||||
// --- suggested_model_tier 意图 → 档位映射 ---
|
||||
|
||||
#[test]
|
||||
fn model_tier_always_none_for_now() {
|
||||
// 接口预留:当前所有意图均返 None
|
||||
fn model_tier_mapping_now_returns_actual() {
|
||||
// Code/Debug/Http → Heavy(复杂推理/重构/排查/多跳调用)
|
||||
for i in [Intent::Code, Intent::Debug, Intent::Http] {
|
||||
assert_eq!(
|
||||
suggested_model_tier(&i),
|
||||
Some(ModelTier::Heavy),
|
||||
"intent {:?} 应映射 Heavy",
|
||||
i
|
||||
);
|
||||
}
|
||||
// File/Search/Task/Idea/Project/Conversation → Standard(默认复杂度)
|
||||
for i in [
|
||||
Intent::Code,
|
||||
Intent::Debug,
|
||||
Intent::File,
|
||||
Intent::Project,
|
||||
Intent::Search,
|
||||
Intent::Task,
|
||||
Intent::Idea,
|
||||
Intent::Project,
|
||||
Intent::Conversation,
|
||||
Intent::Search,
|
||||
Intent::Http,
|
||||
Intent::Chat,
|
||||
Intent::Unknown,
|
||||
] {
|
||||
assert_eq!(suggested_model_tier(&i), None, "intent {:?} 应返 None", i);
|
||||
assert_eq!(
|
||||
suggested_model_tier(&i),
|
||||
Some(ModelTier::Standard),
|
||||
"intent {:?} 应映射 Standard",
|
||||
i
|
||||
);
|
||||
}
|
||||
// Chat → Fast(简单闲聊)
|
||||
assert_eq!(suggested_model_tier(&Intent::Chat), Some(ModelTier::Fast));
|
||||
// Unknown → None(未识别,不强加 tier 偏好,fallback 上游默认)
|
||||
assert_eq!(suggested_model_tier(&Intent::Unknown), None);
|
||||
}
|
||||
|
||||
// --- IntentRecognizer Default ---
|
||||
@@ -1145,10 +1247,13 @@ mod tests {
|
||||
];
|
||||
for intent in all_intents {
|
||||
let subset = tool_subset_for(&intent);
|
||||
// 全 registry 工具名(四 domain 并集:Data + File + Exec + Http)
|
||||
// 全 registry 工具名(五 domain 并集:Data + DataReadOnly + File + Exec + Http)
|
||||
// 注:DataReadOnly 工具名是 Data 的子集,chain 它仅为语义显式(并集去重无副作用),
|
||||
// 防 Code subset 里 list_projects 等 DataReadOnly 工具被判"不在 registry"。
|
||||
let registry: std::collections::HashSet<&str> = ToolDomain::Data
|
||||
.tools()
|
||||
.iter()
|
||||
.chain(ToolDomain::DataReadOnly.tools().iter())
|
||||
.chain(ToolDomain::File.tools().iter())
|
||||
.chain(ToolDomain::Exec.tools().iter())
|
||||
.chain(ToolDomain::Http.tools().iter())
|
||||
|
||||
Reference in New Issue
Block a user