重构: AI流式断线保文StreamResult三分支+重试对齐决策a1,推进链落df-nodes
This commit is contained in:
@@ -205,51 +205,57 @@ pub fn build_ai_tool_registry(db: &Arc<Database>) -> AiToolRegistry {
|
||||
|
||||
// ── 只读 (Low) ──
|
||||
registry.register(
|
||||
"list_projects", "列出所有项目,返回项目列表(ID、名称、状态、描述)。最多返回 50 条",
|
||||
df_ai::ai_tools::object_schema(vec![]), RiskLevel::Low,
|
||||
{ let db = db.clone(); Box::new(move |_args: serde_json::Value| {
|
||||
"list_projects", "列出所有项目,支持 offset/limit 分页。返回 items(项目列表)、total(总量)、has_more(是否有更多页)。默认 limit=50",
|
||||
df_ai::ai_tools::object_schema(vec![("offset", "integer", false), ("limit", "integer", false)]), RiskLevel::Low,
|
||||
{ let db = db.clone(); Box::new(move |args: serde_json::Value| {
|
||||
let db = db.clone();
|
||||
Box::pin(async move {
|
||||
let repo = df_storage::crud::ProjectRepo::new(&db);
|
||||
let mut items = repo.list_active().await?; // list_active 排除回收站(deleted_at),防 LLM 看到已软删项目
|
||||
let items = repo.list_active().await?; // list_active 排除回收站(deleted_at),防 LLM 看到已软删项目
|
||||
let total = items.len();
|
||||
let truncated = total > MAX_LIST_RESULTS;
|
||||
items.truncate(MAX_LIST_RESULTS);
|
||||
Ok(serde_json::json!({ "items": items, "truncated": truncated }))
|
||||
let offset = args["offset"].as_u64().unwrap_or(0) as usize;
|
||||
let limit = args["limit"].as_u64().unwrap_or(MAX_LIST_RESULTS as u64).min(MAX_LIST_RESULTS as u64) as usize;
|
||||
let page_items: Vec<_> = items.into_iter().skip(offset).take(limit).collect();
|
||||
let has_more = (offset + page_items.len()) < total;
|
||||
Ok(serde_json::json!({ "items": page_items, "total": total, "has_more": has_more }))
|
||||
})
|
||||
})},
|
||||
);
|
||||
registry.register(
|
||||
"list_tasks", "列出任务,可按 project_id 筛选。最多返回 50 条",
|
||||
df_ai::ai_tools::object_schema(vec![("project_id", "string", false)]), RiskLevel::Low,
|
||||
"list_tasks", "列出任务,可按 project_id 筛选,支持 offset/limit 分页。返回 items、total、has_more。默认 limit=50",
|
||||
df_ai::ai_tools::object_schema(vec![("project_id", "string", false), ("offset", "integer", false), ("limit", "integer", false)]), RiskLevel::Low,
|
||||
{ let db = db.clone(); Box::new(move |args: serde_json::Value| {
|
||||
let db = db.clone();
|
||||
Box::pin(async move {
|
||||
let repo = df_storage::crud::TaskRepo::new(&db);
|
||||
let mut tasks = if let Some(pid) = args.get("project_id").and_then(|v| v.as_str()) {
|
||||
let tasks = if let Some(pid) = args.get("project_id").and_then(|v| v.as_str()) {
|
||||
repo.query("project_id", pid).await?
|
||||
} else {
|
||||
repo.list_all().await?
|
||||
};
|
||||
let total = tasks.len();
|
||||
let truncated = total > MAX_LIST_RESULTS;
|
||||
tasks.truncate(MAX_LIST_RESULTS);
|
||||
Ok(serde_json::json!({ "items": tasks, "truncated": truncated }))
|
||||
let offset = args["offset"].as_u64().unwrap_or(0) as usize;
|
||||
let limit = args["limit"].as_u64().unwrap_or(MAX_LIST_RESULTS as u64).min(MAX_LIST_RESULTS as u64) as usize;
|
||||
let page_items: Vec<_> = tasks.into_iter().skip(offset).take(limit).collect();
|
||||
let has_more = (offset + page_items.len()) < total;
|
||||
Ok(serde_json::json!({ "items": page_items, "total": total, "has_more": has_more }))
|
||||
})
|
||||
})},
|
||||
);
|
||||
registry.register(
|
||||
"list_ideas", "列出所有灵感。最多返回 50 条",
|
||||
df_ai::ai_tools::object_schema(vec![]), RiskLevel::Low,
|
||||
{ let db = db.clone(); Box::new(move |_args: serde_json::Value| {
|
||||
"list_ideas", "列出所有灵感,支持 offset/limit 分页。返回 items、total、has_more。默认 limit=50",
|
||||
df_ai::ai_tools::object_schema(vec![("offset", "integer", false), ("limit", "integer", false)]), RiskLevel::Low,
|
||||
{ let db = db.clone(); Box::new(move |args: serde_json::Value| {
|
||||
let db = db.clone();
|
||||
Box::pin(async move {
|
||||
let repo = df_storage::crud::IdeaRepo::new(&db);
|
||||
let mut items = repo.list_all().await?;
|
||||
let items = repo.list_all().await?;
|
||||
let total = items.len();
|
||||
let truncated = total > MAX_LIST_RESULTS;
|
||||
items.truncate(MAX_LIST_RESULTS);
|
||||
Ok(serde_json::json!({ "items": items, "truncated": truncated }))
|
||||
let offset = args["offset"].as_u64().unwrap_or(0) as usize;
|
||||
let limit = args["limit"].as_u64().unwrap_or(MAX_LIST_RESULTS as u64).min(MAX_LIST_RESULTS as u64) as usize;
|
||||
let page_items: Vec<_> = items.into_iter().skip(offset).take(limit).collect();
|
||||
let has_more = (offset + page_items.len()) < total;
|
||||
Ok(serde_json::json!({ "items": page_items, "total": total, "has_more": has_more }))
|
||||
})
|
||||
})},
|
||||
);
|
||||
@@ -456,16 +462,19 @@ pub fn build_ai_tool_registry(db: &Arc<Database>) -> AiToolRegistry {
|
||||
})},
|
||||
);
|
||||
registry.register(
|
||||
"list_trash", "列出回收站已删除项目(最多返回 50 条)",
|
||||
df_ai::ai_tools::object_schema(vec![]), RiskLevel::Low,
|
||||
{ let db = db.clone(); Box::new(move |_args: serde_json::Value| {
|
||||
"list_trash", "列出回收站已删除项目,支持 offset/limit 分页。返回 items、total、has_more。默认 limit=50",
|
||||
df_ai::ai_tools::object_schema(vec![("offset", "integer", false), ("limit", "integer", false)]), RiskLevel::Low,
|
||||
{ let db = db.clone(); Box::new(move |args: serde_json::Value| {
|
||||
let db = db.clone();
|
||||
Box::pin(async move {
|
||||
let repo = df_storage::crud::ProjectRepo::new(&db);
|
||||
let mut items = repo.list_deleted().await?;
|
||||
let truncated = items.len() > MAX_LIST_RESULTS;
|
||||
items.truncate(MAX_LIST_RESULTS);
|
||||
Ok(serde_json::json!({ "items": items, "truncated": truncated }))
|
||||
let items = repo.list_deleted().await?;
|
||||
let total = items.len();
|
||||
let offset = args["offset"].as_u64().unwrap_or(0) as usize;
|
||||
let limit = args["limit"].as_u64().unwrap_or(MAX_LIST_RESULTS as u64).min(MAX_LIST_RESULTS as u64) as usize;
|
||||
let page_items: Vec<_> = items.into_iter().skip(offset).take(limit).collect();
|
||||
let has_more = (offset + page_items.len()) < total;
|
||||
Ok(serde_json::json!({ "items": page_items, "total": total, "has_more": has_more }))
|
||||
})
|
||||
})},
|
||||
);
|
||||
@@ -568,23 +577,27 @@ pub fn build_ai_tool_registry(db: &Arc<Database>) -> AiToolRegistry {
|
||||
}
|
||||
anyhow::bail!("读取文件失败: {}", e);
|
||||
}
|
||||
// search 模式: 按行枚举收集含 search 子串的行,限 50 条
|
||||
// search 模式: 按行枚举收集含 search 子串的行,支持 offset/limit 分页
|
||||
if let Some(search) = args["search"].as_str() {
|
||||
let search_max = 50usize;
|
||||
let mut matches_vec: Vec<serde_json::Value> = Vec::with_capacity(search_max);
|
||||
const SEARCH_MAX: usize = 50;
|
||||
let search_offset = args["offset"].as_u64().unwrap_or(0) as usize;
|
||||
let search_limit = args["limit"].as_u64().unwrap_or(SEARCH_MAX as u64).min(SEARCH_MAX as u64) as usize;
|
||||
// 先收集全部匹配行用于 total 计数,再做 skip/take 分页
|
||||
let mut all_matches: Vec<serde_json::Value> = Vec::new();
|
||||
for (idx, line) in content.lines().enumerate() {
|
||||
if line.contains(search) {
|
||||
matches_vec.push(serde_json::json!({ "line": idx + 1, "content": line }));
|
||||
if matches_vec.len() >= search_max { break; }
|
||||
all_matches.push(serde_json::json!({ "line": idx + 1, "content": line }));
|
||||
}
|
||||
}
|
||||
let total = content.lines().filter(|l| l.contains(search)).count();
|
||||
let total = all_matches.len();
|
||||
let page_matches: Vec<_> = all_matches.into_iter().skip(search_offset).take(search_limit).collect();
|
||||
let has_more = (search_offset + page_matches.len()) < total;
|
||||
return Ok(serde_json::json!({
|
||||
"path": path, "size": metadata.len(),
|
||||
"search": search,
|
||||
"matches": matches_vec,
|
||||
"matches": page_matches,
|
||||
"total": total,
|
||||
"has_more": total > search_max,
|
||||
"has_more": has_more,
|
||||
}));
|
||||
}
|
||||
// 默认分页模式: limit 硬上限 2000 行(防 LLM 传超大 limit 读全文件,1MB 限下仍可能数万行)
|
||||
@@ -1043,8 +1056,8 @@ pub fn build_ai_tool_registry(db: &Arc<Database>) -> AiToolRegistry {
|
||||
|
||||
// ── 文件搜索 (Low risk) ──
|
||||
registry.register(
|
||||
"search_files", "在指定目录下搜索匹配模式(字符串包含匹配)的文件名,返回路径和大小列表。支持递归搜索,结果限 50 条",
|
||||
df_ai::ai_tools::object_schema(vec![("path", "string", true), ("pattern", "string", true), ("recursive", "boolean", false)]),
|
||||
"search_files", "在指定目录下搜索匹配模式(字符串包含匹配)的文件名,支持 offset/limit 分页。返回 results、total、has_more。默认 limit=50",
|
||||
df_ai::ai_tools::object_schema(vec![("path", "string", true), ("pattern", "string", true), ("recursive", "boolean", false), ("offset", "integer", false), ("limit", "integer", false)]),
|
||||
RiskLevel::Low,
|
||||
Box::new(|args: serde_json::Value| Box::pin(async move {
|
||||
let resolved = resolve_workspace_path(
|
||||
@@ -1055,14 +1068,49 @@ pub fn build_ai_tool_registry(db: &Arc<Database>) -> AiToolRegistry {
|
||||
let recursive = args["recursive"].as_bool().unwrap_or(false);
|
||||
let pattern_lower = pattern.to_lowercase();
|
||||
const MAX_RESULTS: usize = 50;
|
||||
let mut results = Vec::new();
|
||||
let offset = args["offset"].as_u64().unwrap_or(0) as usize;
|
||||
let limit = args["limit"].as_u64().unwrap_or(MAX_RESULTS as u64).min(MAX_RESULTS as u64) as usize;
|
||||
// 先收集全部结果用于 total 计数,再做 skip/take 分页
|
||||
let mut all_results = Vec::new();
|
||||
let mut total = 0u64;
|
||||
search_files_recursive(path, &pattern_lower, recursive, 0, 5, MAX_RESULTS, &mut results, &mut total).await?;
|
||||
let has_more = total as usize > MAX_RESULTS;
|
||||
Ok(serde_json::json!({ "path": path, "pattern": pattern, "results": results, "total": total, "has_more": has_more }))
|
||||
// 用较大上限收集全量(分页由内存 skip/take 控制)
|
||||
search_files_recursive(path, &pattern_lower, recursive, 0, 5, offset + limit, &mut all_results, &mut total).await?;
|
||||
let page_results: Vec<_> = all_results.into_iter().skip(offset).take(limit).collect();
|
||||
let has_more = (offset + page_results.len()) < total as usize;
|
||||
Ok(serde_json::json!({ "path": path, "pattern": pattern, "results": page_results, "total": total, "has_more": has_more }))
|
||||
})),
|
||||
);
|
||||
|
||||
// ── 探总量工具 (Low risk, F-260616-08) ──
|
||||
registry.register(
|
||||
"get_project_count", "获取项目总数(未删除项目),用于分页策略判断。返回 { total: usize }",
|
||||
df_ai::ai_tools::object_schema(vec![]), RiskLevel::Low,
|
||||
{ let db = db.clone(); Box::new(move |_args: serde_json::Value| {
|
||||
let db = db.clone();
|
||||
Box::pin(async move {
|
||||
let repo = df_storage::crud::ProjectRepo::new(&db);
|
||||
let items = repo.list_active().await?;
|
||||
Ok(serde_json::json!({ "total": items.len() }))
|
||||
})
|
||||
})},
|
||||
);
|
||||
registry.register(
|
||||
"get_task_count", "获取任务总数(未删除任务),用于分页策略判断。返回 { total: usize }",
|
||||
df_ai::ai_tools::object_schema(vec![("project_id", "string", false)]), RiskLevel::Low,
|
||||
{ let db = db.clone(); Box::new(move |args: serde_json::Value| {
|
||||
let db = db.clone();
|
||||
Box::pin(async move {
|
||||
let repo = df_storage::crud::TaskRepo::new(&db);
|
||||
let tasks = if let Some(pid) = args.get("project_id").and_then(|v| v.as_str()) {
|
||||
repo.query("project_id", pid).await?
|
||||
} else {
|
||||
repo.list_all().await?
|
||||
};
|
||||
Ok(serde_json::json!({ "total": tasks.len() }))
|
||||
})
|
||||
})},
|
||||
);
|
||||
|
||||
registry
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user