// src/agent/tools/astro/research/paper_outline.rs // GetPaperOutlineTool — 提取文献章节大纲(目录) use async_trait::async_trait; use serde_json::json; use tracing::info; use crate::agent::tools::{AgentTool, ToolContext, ToolOutput}; pub struct GetPaperOutlineTool; #[async_trait] impl AgentTool for GetPaperOutlineTool { fn name(&self) -> &str { "get_paper_outline" } fn description(&self) -> &str { "提取已解析文献的章节大纲(目录)。返回所有章节标题、层级和序号,供后续按章节读取使用。\ 适用于:快速浏览文献结构、定位感兴趣的章节。配合 get_paper_content 的 section_index/section_name 参数使用。" } fn parameters(&self) -> serde_json::Value { json!({ "type": "object", "properties": { "bibcode": { "type": "string", "description": "文献的唯一标识符,支持 ADS Bibcode、DOI 或 arXiv ID" } }, "required": ["bibcode"] }) } fn group(&self) -> &str { "as:research" } fn is_readonly(&self) -> bool { true } fn is_concurrency_safe(&self, _args: &serde_json::Value) -> bool { true } async fn execute(&self, args: serde_json::Value, ctx: &ToolContext) -> ToolOutput { let bibcode = match args.get("bibcode").and_then(|b| b.as_str()) { Some(b) => b.to_string(), None => return ToolOutput::error("缺少必需参数 'bibcode'"), }; info!("[GetPaperOutline] 提取大纲: {}", bibcode); let state = &ctx.app_state; let paths = match crate::api::helpers::check_paper_paths_in_db( &state.db, &state.config.library_dir, &bibcode, ) .await { Ok(Some((_, _, md_opt, _))) => md_opt, Ok(None) => return ToolOutput::error( "获取大纲失败:该文献未在本地数据库中注册。请先使用 search_papers 搜索该文献。", ), Err(e) => return ToolOutput::error(format!("获取大纲失败: {}", e)), }; let md_rel = match paths { Some(rel) => rel, None => { return ToolOutput::error( "获取大纲失败:该文献尚未完成结构化解析。请先使用 process_paper 执行 download 和 parse 任务。", ) } }; let md_abs = state.config.library_dir.join(&md_rel); let content = match std::fs::read_to_string(&md_abs) { Ok(c) => c, Err(e) => return ToolOutput::error(format!("获取大纲失败,读取文件错误: {}", e)), }; let sections = crate::services::section_parser::extract_outline(&content, 3); if sections.is_empty() { return ToolOutput::success( "该文献未检测到章节标题(## 或 ### 格式)。请使用 get_paper_content 直接读取全文。", json!({ "bibcode": bibcode, "sections": [] }), ); } let display_sections: Vec = sections .iter() .map(|s| { json!({ "index": s.index, "level": s.level, "heading": s.heading, }) }) .collect(); let content_display = sections .iter() .map(|s| { let prefix = "#".repeat(s.level); format!( "[{}] {} {} ({} 字符)", s.index, prefix, s.heading, s.char_end - s.char_start ) }) .collect::>() .join("\n"); let header = format!("文献 {} 章节大纲(共 {} 节):\n", bibcode, sections.len()); let footer = "\n\n使用 get_paper_content 并传入 section_index 或 section_name 读取指定章节内容。"; ToolOutput::success( header + &content_display + footer, json!({ "bibcode": bibcode, "section_count": sections.len(), "sections": display_sections }), ) } }