// src/agent/tools/target.rs — 天体物理查询工具 use async_trait::async_trait; use serde_json::json; use tracing::info; use crate::agent::tools::{AgentTool, ToolContext, ToolOutput}; /// 天体信息查询工具:通过 CDS Sesame 查询天体物理属性 pub struct QueryTargetTool; #[async_trait] impl AgentTool for QueryTargetTool { fn name(&self) -> &str { "query_target" } fn description(&self) -> &str { "查询天体的基本物理属性信息。输入天体名称,返回坐标 (RA/Dec)、视星等、光谱型、视差等属性。\ 数据来源为 CDS SIMBAD/Sesame 名称解析服务。适用于:获取天体基本参数、验证天体身份。" } fn parameters(&self) -> serde_json::Value { json!({ "type": "object", "properties": { "object_name": { "type": "string", "description": "天体名称,如 'NGC 6752', 'GD 358', 'HD 209458', 'M 31' 等" } }, "required": ["object_name"] }) } /// 纯读取外部天体数据库,并发安全 fn is_concurrency_safe(&self, _args: &serde_json::Value) -> bool { true } async fn execute(&self, args: serde_json::Value, ctx: &ToolContext) -> ToolOutput { let object_name = match args.get("object_name").and_then(|n| n.as_str()) { Some(n) => n.to_string(), None => return ToolOutput::error("缺少必需参数 'object_name'"), }; info!("[QueryTarget] 查询天体: {}", object_name); let state = &ctx.app_state; let client = reqwest::Client::new(); match crate::services::target::query_target_cached(&state.db, &object_name, None, &client) .await { Ok(info) => { let content = format!( "天体: {}\nRA: {}\nDec: {}\n视差: {}\n光谱型: {}\nV星等: {}\n别名: {}", info.target_name, info.ra.as_deref().unwrap_or("未知"), info.dec.as_deref().unwrap_or("未知"), info.parallax .map(|p| format!("{:.4} mas", p)) .unwrap_or_else(|| "未知".to_string()), info.spectral_type.as_deref().unwrap_or("未知"), info.v_magnitude .map(|v| format!("{:.2}", v)) .unwrap_or_else(|| "未知".to_string()), if info.aliases.is_empty() { "无".to_string() } else { info.aliases.join(", ") } ); ToolOutput::success( content, json!({ "target_name": info.target_name, "ra": info.ra, "dec": info.dec, "parallax": info.parallax, "spectral_type": info.spectral_type, "v_magnitude": info.v_magnitude, "aliases": info.aliases }), ) } Err(e) => ToolOutput::error(format!("天体 '{}' 查询失败: {}", object_name, e)), } } fn is_readonly(&self) -> bool { true } }